GVKun编程网logo

React 16 的新特性(react16.8新特性)

15

针对React16的新特性和react16.8新特性这两个问题,本篇文章进行了详细的解答,同时本文还将给你拓展03、React系列之--ES6的新特性、30分钟精通React今年最劲爆的新特性——Re

针对React 16 的新特性react16.8新特性这两个问题,本篇文章进行了详细的解答,同时本文还将给你拓展03、React系列之--ES6的新特性、30分钟精通React今年最劲爆的新特性——React Hooks、C++的新特性for-each、Java16的新特性等相关知识,希望可以帮助到你。

本文目录一览:

React 16 的新特性(react16.8新特性)

React 16 的新特性(react16.8新特性)

前段时间 React 的 16 版本发布了,采用了 MIT 开源许可证,新增了一些新的特性。

  1. Error Boundary
  2. render 方法新增返回类型
  3. Portals
  4. 支持自定义 DOM 属性
  5. setState 传入 null 时不会再触发更新
  6. 更好的服务器端渲染
  7. 新的打包策略
  8. ...

1. 使用 Error Boundary 处理错误组件

之前,一旦某个组件发生错误,整个组件树将会从根节点被 unmount 下来。React 16 修复了这一点,引入了 Error Boundary 的概念,中文译为 “错误边界”,当某个组件发生错误时,我们可以通过 Error Boundary 捕获到错误并对错误做优雅处理,如使用 Error Boundary 提供的内容替代错误组件。Error Boundary 可以看作是一种特殊的 React 组件,新增了 componentDidCatch 这个生命周期函数,它可以捕获自身及子树上的错误并对错误做优雅处理,包括上报错误日志、展示出错提示,而不是卸载整个组件树。(注:它并不能捕获 runtime 所有的错误,比如组件回调事件里的错误,可以把它想象成传统的 try-catch 语句)

//最佳实践:将ErrorBoundary抽象为一个公用的组件类

import React, { Component } from ''react''

export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
  }
  componentDidCatch(err, info) {
    this.setState({ hasError: true })
    //sendErrorReport(err,info)
  }
  render(){
    if(this.state.hasError){
      return <div>Something went wrong!</div>
    }
    return this.props.children
  }
}

我们可以在容易出错的组件外使用 ErrorBoundary 将它包裹起来,如下

//使用方式
render(){
  return (
    <div>
      <ErrorBoundary>
        <Profile user={this.state.user} />
      </ErrorBoundary>
      <button onClick={this.onClick}>Update</button>
    </div>
  )
}

如果 Profile 组件发生错误,将会使用 ErrorBoundary 提供的 <div>Something went wrong</div > 代替它,而不会引起整个组件树的卸载。

2. render 方法新增返回类型

在 React 16 中,render 方法支持直接返回 string,number,boolean,null,portal,以及 fragments (带有 key 属性的数组),这可以在一定程度上减少页面的 DOM 层级。

//string
render(){
  return ''hello,world''
}

//number
render(){
  return 12345
}

//boolean
render(){
  return isTrue?true:false
}

//null
render(){
  return null
}

//fragments,未加key标识符,控制台会出现warning
render(){
  return [
    <div>hello</div>,
    <span>world</span>,
    <p>oh</p>
  ]
}

以上各种类型现在均可以直接在 render 中返回,不需要再在外层包裹一层容器元素,不过在返回的数组类型中,需要在每个元素上加一个唯一且不变的 key 值,否则控制台会报一个 warning。

3. 使用 createPortal 将组件渲染到当前组件树之外

Portals 机制提供了一种最直接的方式可以把一个子组件渲染到父组件渲染的 DOM 树之外。默认情况下,React 组件树和 DOM 树是完全对应的,因此对于一些 Modal,Overlay 之类的组件,通常是将它们放在顶层,但逻辑上它们可能只是属于某个子组件,不利于组件的代码组织。通过使用 createPortal,我们可以将组件渲染到我们想要的任意 DOM 节点中,但该组件依然处在 React 的父组件之内。带来的一个特性就是,在子组件产生的 event 依然可以被 React 父组件捕获,但在 DOM 结构中,它却不是你的父组件。对于组件组织,代码切割来说,这是一个很好的属性。

//实现一个简易蒙层效果,抽象出一个通用的Overlay组件
import React, { Component } from ''react'';
import ReactDOM from ''react-dom'';

export default class Overlay extends Component {
  constructor(props) {
    super(props);
    this.container = document.createElement(''div'');
    document.body.appendChild(this.container);
  }
  componentWillUnmount() {
    document.body.removeChild(this.container);
  }
  render() {
    return ReactDOM.createPortal(
      <div className=''overlay''>
        <span className=''overlay-close'' onClick={this.props.onClose}>×</span>
        {this.props.children}
      </div>,
      this.container
    )
  }
}
//该组件对应的样式如下
.overlay{
  box-sizing:border-box;
  position: fixed;
  top:50%;
  left:50%;
  width:260px;
  height:200px;
  margin-left:-130px;
  margin-top:-100px;
  padding:10px;
  background-color: #fff;
  outline: rgba(0,0,0,.5) solid 9999px;
}
.overlay-close{
  position: absolute;
  top:10px;
  right:10px;
  color:red;
  cursor: pointer;
}

使用方式如下:

class App extends Component {
 constructor(props) {
  super(props);
  this.state = {
   overlayActive: false
  }
  this.closeOverlay = this.closeOverlay.bind(this);
  this.showOverlay = this.showOverlay.bind(this);
 }
 closeOverlay() {
  this.setState({ overlayActive: false })
 }
 showOverlay() {
  this.setState({ overlayActive: true })
 }
 render() {
  return (
   <div className="App">
    <div>hello world!</div>
    {this.state.overlayActive &&
     <Overlay onClose={this.closeOverlay}>overlay content</Overlay>}
    <button onClick={this.showOverlay}>show</button>
   </div>
  );
 }
}

效果如图:

4. 支持自定义 DOM 属性

在之前的版本中,React 会忽略无法识别的 HTML 和 SVG 属性,自定义属性只能通过 data-* 形式添加,现在它会把这些属性直接传递给 DOM(这个改动让 React 可以去掉属性白名单,从而减少了文件大小),不过有些写法仍然是无效的。如 DOM 传递的自定义属性是函数类型或 event handler 时,依然会被 React 忽略。

//错误写法
render(){
  return(
    <div a={()=>{}} onclick={this.showOverlay}></div>
  )
)
//Warning: Invalid event handler property `onclick`. Did you mean `onClick`?
//Warning: Invalid value for prop `a` on <div> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.

现在 class 和 tabindex 等属性可以被传递给 DOM,但依然会报一个 Warning,建议使用标准的驼峰式 className,tabIndex 等。

5.setState 传入 null 时不会再触发更新

比如在一个选择城市的函数中,当点击某个城市时,newValue 的值可能发生改变,也可能是点击了原来的城市,值没有变化,返回 null 则可以直接避免触发更新,不会引起重复渲染,不需要在 shouldComponentUpdate 函数里面去判断。

selectCity(e){
  const newValue = e.target.value;
  this.setState((state)=>{
    if(state.city===newValue){
      return null;
    }
    return {city:newValue}
  })
)

注意:现在 setState 回调(第二个参数)会在 componentDidMount/componentDidUpdate 后立即触发,而不是等到所有组件渲染完成之后。

6. 更好的服务器端渲染

React 16 的 SSR 被完全重写,新的实现非常快,接近 3 倍性能于 React 15,现在提供一种流模式 streaming,可以更快地把渲染的字节发送到客户端。另外,React 16 在 hydrating (注:指在客户端基于服务器返回的 HTML 再次重新渲染)方面也表现的更好,React 16 不再要求客户端的初始渲染完全和服务器返回的渲染结果一致,而是尽量重用已经存在的 DOM 元素。不会再有 checksum(标记验证)!并对不一致发出警告。一般来说,在服务器和客户端渲染不同的内容是不建议的,但这样做在某些情况下也是有用的(比如,生成 timestamp)。

7. 新的打包策略

新的打包策略中去掉了 process.env 检查。

React 16 的体积比上个版本减小了 32%(30% post-gzip),文件尺寸的减小一部分要归功于打包方法的改变。

react is 5.3 kb (2.2 kb gzipped), down from 20.7 kb (6.9 kb gzipped).
react-dom is 103.7 kb (32.6 kb gzipped), down from 141 kb (42.9 kb gzipped).
react + react-dom is 109 kb (34.8 kb gzipped), down from 161.7 kb (49.8 kb gzipped).

写在最后,React 16 采用了新的核心架构 React Fiber。官方解释是 “React Fiber 是对核心算法的一次重新实现”,后续再深入学习

03、React系列之--ES6的新特性

03、React系列之--ES6的新特性

等待添加

30分钟精通React今年最劲爆的新特性——React Hooks

30分钟精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?
——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。

你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?
——拥有了Hooks,生命周期钩子函数可以先丢一边了。

你在还在为组件中的this指向而晕头转向吗?
——既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this。

这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。如果你也对react感兴趣,或者正在使用react进行项目开发,答应我,请一定抽出至少30分钟的时间来阅读本文好吗?所有你需要了解的React Hooks的知识点,本文都涉及到了,相信完整读完后你一定会有所收获。

一个最简单的Hooks

首先让我们看一下一个简单的有状态组件:

class Example extends React.Component {
 constructor(props) {
 super(props);
 this.state = {
  count: 0
 };
 }

 render() {
 return (
  <div>
  <p>You clicked {this.state.count} times</p>
  <button onClick={() => this.setState({ count: this.state.count + 1 })}>
   Click me
  </button>
  </div>
 );
 }
}

我们再来看一下使用hooks后的版本:

import { useState } from ''react'';

function Example() {
 const [count, setCount] = useState(0);

 return (
 <div>
  <p>You clicked {count} times</p>
  <button onClick={() => setCount(count + 1)}>
  Click me
  </button>
 </div>
 );
}

是不是简单多了!可以看到,Example变成了一个函数,但这个函数却有自己的状态(count),同时它还可以更新自己的状态(setCount)。这个函数之所以这么了不得,就是因为它注入了一个hook--useState,就是这个hook让我们的函数变成了一个有状态的函数。

除了useState这个hook外,还有很多别的hook,比如useEffect提供了类似于componentDidMount等生命周期钩子的功能,useContext提供了上下文(context)的功能等等。

Hooks本质上就是一类特殊的函数,它们可以为你的函数型组件(function component)注入一些特殊的功能。咦?这听起来有点像被诟病的Mixins啊?难道是Mixins要在react中死灰复燃了吗?当然不会了,等会我们再来谈两者的区别。总而言之,这些hooks的目标就是让你不再写class,让function一统江湖。

React为什么要搞一个Hooks?

想要复用一个有状态的组件太麻烦了!

我们都知道react都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。

那之前,官方推荐怎么解决这个问题呢?答案是:渲染属性(Render Props)和高阶组件(Higher-Order Components)。我们可以稍微跑下题简单看一下这两种模式。

渲染属性指的是使用一个值为函数的prop来传递需要动态渲染的nodes或组件。如下面的代码可以看到我们的DataProvider组件包含了所有跟状态相关的代码,而Cat组件则可以是一个单纯的展示型组件,这样一来DataProvider就可以单独复用了。

import Cat from ''components/cat''
class DataProvider extends React.Component {
 constructor(props) {
 super(props);
 this.state = { target: ''Zac'' };
 }

 render() {
 return (
  <div>
  {this.props.render(this.state)}
  </div>
 )
 }
}

<DataProvider render={data => (
 <Cat target={data.target} />
)}/>

虽然这个模式叫Render Props,但不是说非用一个叫render的props不可,习惯上大家更常写成下面这种:

...
<DataProvider>
 {data => (
 <Cat target={data.target} />
 )}
</DataProvider>

高阶组件这个概念就更好理解了,说白了就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。看下面的代码示例,withUser函数就是一个高阶组件,它返回了一个新的组件,这个组件具有了它提供的获取用户信息的功能。

const withUser = WrappedComponent => {
 const user = sessionStorage.getItem("user");
 return props => <WrappedComponent user={user} {...props} />;
};

const UserPage = props => (
 <div>
 <p>My name is {props.user}!</p>
 </div>
);

export default withUser(UserPage);

以上这两种模式看上去都挺不错的,很多库也运用了这种模式,比如我们常用的React Router。但我们仔细看这两种模式,会发现它们会增加我们代码的层级关系。最直观的体现,打开devtool看看你的组件层级嵌套是不是很夸张吧。这时候再回过头看我们上一节给出的hooks例子,是不是简洁多了,没有多余的层级嵌套。把各种想要的功能写成一个一个可复用的自定义hook,当你的组件想用什么功能时,直接在组件里调用这个hook即可。

生命周期钩子函数里的逻辑太乱了吧!

我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。比如我们需要在componentDidMount中发起ajax请求获取数据,绑定一些事件监听等等。同时,有时候我们还需要在componentDidUpdate做一遍同样的事情。当项目变复杂后,这一块的代码也变得不那么直观。

classes真的太让人困惑了!

我们用class来创建react组件时,还有一件很麻烦的事情,就是this的指向问题。为了保证this的指向正确,我们要经常写这样的代码:this.handleClick = this.handleClick.bind(this),或者是这样的代码:<button onClick={() => this.handleClick(e)}>。一旦我们不小心忘了绑定this,各种bug就随之而来,很麻烦。

还有一件让我很苦恼的事情。我在之前的react系列文章当中曾经说过,尽可能把你的组件写成无状态组件的形式,因为它们更方便复用,可独立测试。然而很多时候,我们用function写了一个简洁完美的无状态组件,后来因为需求变动这个组件必须得有自己的state,我们又得很麻烦的把function改成class。

在这样的背景下,Hooks便横空出世了!

什么是State Hooks?

回到一开始我们用的例子,我们分解来看到底state hooks做了什么:

import { useState } from ''react'';

function Example() {
 const [count, setCount] = useState(0);

 return (
 <div>
  <p>You clicked {count} times</p>
  <button onClick={() => setCount(count + 1)}>
  Click me
  </button>
 </div>
 );
}

声明一个状态变量

import { useState } from ''react'';

function Example() {
 const [count, setCount] = useState(0);

useState是react自带的一个hook函数,它的作用就是用来声明状态变量。useState这个函数接收的参数是我们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是可以改变状态值的方法函数。

所以我们做的事情其实就是,声明了一个状态变量count,把它的初始值设为0,同时提供了一个可以更改count的函数setCount。

上面这种表达形式,是借用了es6的数组解构(array destructuring),它可以让我们的代码看起来更简洁。不清楚这种用法的可以先去看下我的这篇文章30分钟掌握ES6/ES2015核心内容(上)。

如果不用数组解构的话,可以写成下面这样。实际上数组解构是一件开销很大的事情,用下面这种写法,或者改用对象解构,性能会有很大的提升。具体可以去这篇文章的分析Array destructuring for multi-value returns (in light of React hooks),这里不详细展开,我们就按照官方推荐使用数组解构就好。

let _useState = useState(0);
let count = _useState[0];
let setCount = _useState[1];

读取状态值

<p>You clicked {count} times</p>

是不是超简单?因为我们的状态count就是一个单纯的变量而已,我们再也不需要写成{this.state.count}这样了。

更新状态

 <button onClick={() => setCount(count + 1)}>
 Click me
 </button>

当用户点击按钮时,我们调用setCount函数,这个函数接收的参数是修改过的新状态值。接下来的事情就交给react了,react将会重新渲染我们的Example组件,并且使用的是更新后的新的状态,即count=1。这里我们要停下来思考一下,Example本质上也是一个普通的函数,为什么它可以记住之前的状态?

一个至关重要的问题

这里我们就发现了问题,通常来说我们在一个函数中声明的变量,当函数运行完成后,这个变量也就销毁了(这里我们先不考虑闭包等情况),比如考虑下面的例子:

function add(n) {
 const result = 0;
 return result + 1;
}

add(1); //1
add(1); //1

不管我们反复调用add函数多少次,结果都是1。因为每一次我们调用add时,result变量都是从初始值0开始的。那为什么上面的Example函数每次执行的时候,都是拿的上一次执行完的状态值作为初始值?答案是:是react帮我们记住的。至于react是用什么机制记住的,我们可以再思考一下。

假如一个组件有多个状态值怎么办?

首先,useState是可以多次调用的,所以我们完全可以这样写:

function ExampleWithManyStates() {
 const [age, setAge] = useState(42);
 const [fruit, setFruit] = useState(''banana'');
 const [todos, setTodos] = useState([{ text: ''Learn Hooks'' }]);

其次,useState接收的初始值没有规定一定要是string/number/boolean这种简单数据类型,它完全可以接收对象或者数组作为参数。唯一需要注意的点是,之前我们的this.setState做的是合并状态后返回一个新状态,而useState是直接替换老状态后返回新状态。最后,react也给我们提供了一个useReducer的hook,如果你更喜欢redux式的状态管理方案的话。

从ExampleWithManyStates函数我们可以看到,useState无论调用多少次,相互之间是独立的。这一点至关重要。为什么这么说呢?

其实我们看hook的“形态”,有点类似之前被官方否定掉的Mixins这种方案,都是提供一种“插拔式的功能注入”的能力。而mixins之所以被否定,是因为Mixins机制是让多个Mixins共享一个对象的数据空间,这样就很难确保不同Mixins依赖的状态不发生冲突。

而现在我们的hook,一方面它是直接用在function当中,而不是class;另一方面每一个hook都是相互独立的,不同组件调用同一个hook也能保证各自状态的独立性。这就是两者的本质区别了。

react是怎么保证多个useState的相互独立的?

还是看上面给出的ExampleWithManyStates例子,我们调用了三次useState,每次我们传的参数只是一个值(如42,‘banana''),我们根本没有告诉react这些值对应的key是哪个,那react是怎么保证这三个useState找到它对应的state呢?

答案是,react是根据useState出现的顺序来定的。我们具体来看一下:

 //第一次渲染
 useState(42); //将age初始化为42
 useState(''banana''); //将fruit初始化为banana
 useState([{ text: ''Learn Hooks'' }]); //...

 //第二次渲染
 useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
 useState(''banana''); //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
 useState([{ text: ''Learn Hooks'' }]); //...

假如我们改一下代码:

let showFruit = true;
function ExampleWithManyStates() {
 const [age, setAge] = useState(42);
 
 if(showFruit) {
 const [fruit, setFruit] = useState(''banana'');
 showFruit = false;
 }
 
 const [todos, setTodos] = useState([{ text: ''Learn Hooks'' }]);

这样一来,

 //第一次渲染
 useState(42); //将age初始化为42
 useState(''banana''); //将fruit初始化为banana
 useState([{ text: ''Learn Hooks'' }]); //...

 //第二次渲染
 useState(42); //读取状态变量age的值(这时候传的参数42直接被忽略)
 // useState(''banana''); 
 useState([{ text: ''Learn Hooks'' }]); //读取到的却是状态变量fruit的值,导致报错

鉴于此,react规定我们必须把hooks写在函数的最外层,不能写在ifelse等条件语句当中,来确保hooks的执行顺序一致。

什么是Effect Hooks?

我们在上一节的例子中增加一个新功能:

import { useState, useEffect } from ''react'';

function Example() {
 const [count, setCount] = useState(0);

 // 类似于componentDidMount 和 componentDidUpdate:
 useEffect(() => {
 // 更新文档的标题
 document.title = `You clicked ${count} times`;
 });

 return (
 <div>
  <p>You clicked {count} times</p>
  <button onClick={() => setCount(count + 1)}>
  Click me
  </button>
 </div>
 );
}

我们对比着看一下,如果没有hooks,我们会怎么写?

class Example extends React.Component {
 constructor(props) {
 super(props);
 this.state = {
  count: 0
 };
 }

 componentDidMount() {
 document.title = `You clicked ${this.state.count} times`;
 }

 componentDidUpdate() {
 document.title = `You clicked ${this.state.count} times`;
 }

 render() {
 return (
  <div>
  <p>You clicked {this.state.count} times</p>
  <button onClick={() => this.setState({ count: this.state.count + 1 })}>
   Click me
  </button>
  </div>
 );
 }
}

我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。

同时,由于前文所说hooks可以反复多次使用,相互独立。所以我们合理的做法是,给每一个副作用一个单独的useEffect钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰。

useEffect做了什么?

我们再梳理一遍下面代码的逻辑:

function Example() {
 const [count, setCount] = useState(0);

 useEffect(() => {
 document.title = `You clicked ${count} times`;
 });

首先,我们声明了一个状态变量count,将它的初始值设为0。然后我们告诉react,我们的这个组件有一个副作用。我们给useEffecthook传了一个匿名函数,这个匿名函数就是我们的副作用。在这个例子里,我们的副作用是调用browser API来修改文档标题。当react要渲染我们的组件时,它会先记住我们用到的副作用。等react更新了DOM之后,它再依次执行我们定义的副作用函数。

这里要注意几点:

第一,react首次渲染和之后的每次渲染都会调用一遍传给useEffect的函数。而之前我们要用两个声明周期函数来分别表示首次渲染(componentDidMount),和之后的更新导致的重新渲染(componentDidUpdate)。

第二,useEffect中定义的副作用函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而之前的componentDidMount或componentDidUpdate中的代码则是同步执行的。这种安排对大多数副作用说都是合理的,但有的情况除外,比如我们有时候需要先根据DOM计算出某个元素的尺寸再重新渲染,这时候我们希望这次重新渲染是同步发生的,也就是说它会在浏览器真的去绘制这个页面前发生。

useEffect怎么解绑一些副作用

这种场景很常见,当我们在componentDidMount里添加了一个注册,我们得马上在componentWillUnmount中,也就是组件被注销之前清除掉我们添加的注册,否则内存泄漏的问题就出现了。

怎么清除呢?让我们传给useEffect的副作用函数返回一个新的函数即可。这个新的函数将会在组件下一次重新渲染之后执行。这种模式在一些pubsub模式的实现中很常见。看下面的例子:

import { useState, useEffect } from ''react'';

function FriendStatus(props) {
 const [isOnline, setIsOnline] = useState(null);

 function handleStatusChange(status) {
 setIsOnline(status.isOnline);
 }

 useEffect(() => {
 ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
 // 一定注意下这个顺序:告诉react在下次重新渲染组件之后,同时是下次调用ChatAPI.subscribeToFriendStatus之前执行cleanup
 return function cleanup() {
  ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
 };
 });

 if (isOnline === null) {
 return ''Loading...'';
 }
 return isOnline ? ''Online'' : ''Offline'';
}

这里有一个点需要重视!这种解绑的模式跟componentWillUnmount不一样。componentWillUnmount只会在组件被销毁前执行一次而已,而useEffect里的函数,每次组件渲染后都会执行一遍,包括副作用函数返回的这个清理函数也会重新执行一遍。所以我们一起来看一下下面这个问题。

为什么要让副作用函数每次组件更新都执行一遍?

我们先看以前的模式:

 componentDidMount() {
 ChatAPI.subscribeToFriendStatus(
  this.props.friend.id,
  this.handleStatusChange
 );
 }

 componentWillUnmount() {
 ChatAPI.unsubscribeFromFriendStatus(
  this.props.friend.id,
  this.handleStatusChange
 );
 }

很清除,我们在componentDidMount注册,再在componentWillUnmount清除注册。但假如这时候props.friend.id变了怎么办?我们不得不再添加一个componentDidUpdate来处理这种情况:

...
 componentDidUpdate(prevProps) {
 // 先把上一个friend.id解绑
 ChatAPI.unsubscribeFromFriendStatus(
  prevProps.friend.id,
  this.handleStatusChange
 );
 // 再重新注册新但friend.id
 ChatAPI.subscribeToFriendStatus(
  this.props.friend.id,
  this.handleStatusChange
 );
 }
...

看到了吗?很繁琐,而我们但useEffect则没这个问题,因为它在每次组件更新后都会重新执行一遍。所以代码的执行顺序是这样的:

1.页面首次渲染
2.替friend.id=1的朋友注册
3.突然friend.id变成了2
4.页面重新渲染
5.清除friend.id=1的绑定
6.替friend.id=2的朋友注册
...

怎么跳过一些不必要的副作用函数

按照上一节的思路,每次重新渲染都要执行一遍这些副作用函数,显然是不经济的。怎么跳过一些不必要的计算呢?我们只需要给useEffect传第二个参数即可。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)。

useEffect(() => {
 document.title = `You clicked ${count} times`;
}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句

当我们第二个参数传一个空数组[]时,其实就相当于只在首次渲染的时候执行。也就是componentDidMount加componentWillUnmount的模式。不过这种用法可能带来bug,少用。

还有哪些自带的Effect Hooks?

除了上文重点介绍的useState和useEffect,react还给我们提供来很多有用的hooks:

  • useContext
  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeMethods
  • useMutationEffect
  • useLayoutEffect

我不再一一介绍,大家自行去查阅官方文档。

怎么写自定义的Effect Hooks?

为什么要自己去写一个Effect Hooks? 这样我们才能把可以复用的逻辑抽离出来,变成一个个可以随意插拔的“插销”,哪个组件要用来,我就插进哪个组件里,so easy!看一个完整的例子,你就明白了。

比如我们可以把上面写的FriendStatus组件中判断朋友是否在线的功能抽出来,新建一个useFriendStatus的hook专门用来判断某个id是否在线。

import { useState, useEffect } from ''react'';

function useFriendStatus(friendID) {
 const [isOnline, setIsOnline] = useState(null);

 function handleStatusChange(status) {
 setIsOnline(status.isOnline);
 }

 useEffect(() => {
 ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
 return () => {
  ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
 };
 });

 return isOnline;
}

这时候FriendStatus组件就可以简写为:

function FriendStatus(props) {
 const isOnline = useFriendStatus(props.friend.id);

 if (isOnline === null) {
 return ''Loading...'';
 }
 return isOnline ? ''Online'' : ''Offline'';
}

简直Perfect!假如这个时候我们又有一个朋友列表也需要显示是否在线的信息:

function FriendListItem(props) {
 const isOnline = useFriendStatus(props.friend.id);

 return (
 <li style={{ color: isOnline ? ''green'' : ''black'' }}>
  {props.friend.name}
 </li>
 );
}

简直Fabulous!

结尾

不知道你阅读完整篇文章的感受如何,或者对hooks有任何角度的看法和思考都欢迎在评论区一起讨论。另外如果你有换工作的打算,我们部门真的很缺人,欢迎私信勾搭~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

您可能感兴趣的文章:
  • react纯函数组件setState更新页面不刷新的解决
  • 示例详解react中useState的用法
  • 使用react的7个避坑案例小结
  • react使用useState修改对象或者数组的值无法改变视图的问题

C++的新特性for-each

C++的新特性for-each

C++实验课要求用for each 循环来实现关联容器 map 的输出,一开始完全萌比。查了好久的资料才整理出下面的:

  C++11新特性之一就是类似java的for each循环:

 1 map<int, string> m;
 2  // 1
 3 for (    auto &v : m)  
 4      {
 5          cout<<v.first<<" "<<v.second<<endl;
 6     }
 7 
 8  // 2 lamda表达式
 9  for_each(m.begin(),m.end(),[](map<int,string>::reference a){
10          cout<<a.first<<" "<<a.second<<endl;
11      });
12 
13 // 3 for_each
14  void fun(map<int,string>::reference a)  //不要少了reference,不然会报错。
15 {
16          cout<<a.first<<" "<<a.second<<endl;
17 }
18 for_each(m.begin(),m.end(),fun);

 

还有一种宏定义的方法: 

1 //定义
2 #define foreach(container,it) \
3     for(typeof((container).begin()) it = (container).begin();it!=(container).end();++it)
4 //输出
5 foreach(m,it)
6     {
7         cout<<it->first<<","<<it->second<<endl;
8     }

 

Java16的新特性

Java16的新特性

本文主要讲述一下Java16的新特性

版本号

java -version
openjdk version "16" 2021-03-16
OpenJDK Runtime Environment (build 16+36-2231)
OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing)

从version信息可以看出是build 16+36

特性列表

JEP 338: Vector API (Incubator)

提供了jdk.incubator.vector来用于矢量计算,实例如下

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorComputation(float[] a, float[] b, float[] c) {

    for (int i = 0; i < a.length; i += SPECIES.length()) {
        var m = SPECIES.indexInRange(i, a.length);
		// FloatVector va, vb, vc;
        var va = FloatVector.fromArray(SPECIES, a, i, m);
        var vb = FloatVector.fromArray(SPECIES, b, i, m);
        var vc = va.mul(va).
                    add(vb.mul(vb)).
                    neg();
        vc.intoArray(c, i, m);
    }
}

JEP 347: Enable C++14 Language Features

在JDK C++的源码中允许使用C++14的语言特性

JEP 357: Migrate from Mercurial to Git

OpenJDK源码的版本控制从Mercurial (hg) 迁移到git

JEP 369: Migrate to GitHub

将OpenJDK源码的版本控制迁移到github上

JEP 376: ZGC: Concurrent Thread-Stack Processing

实现了并发thread-stack处理来降低GC safepoints的负担

JEP 380: Unix-Domain Socket Channels

对socket channel及server socket channel的api提供对unix domain socket的支持

JEP 386: Alpine Linux Port

将glibc的jdk移植到使用musl的alpine linux上

JEP 387: Elastic Metaspace

支持不再使用的class metadata归还内存给操作系统,降低内存占用

JEP 388: Windows/AArch64 Port

移植JDK到Windows/AArch64

JEP 389: Foreign Linker API (Incubator)

提供jdk.incubator.foreign来简化native code的调用

JEP 390: Warnings for Value-Based Classes

提供@jdk.internal.ValueBased来用于标注作为value-based的类,实例如下

@jdk.internal.ValueBased
public final class SomeVbc {

    public SomeVbc() {}

    final String ref = "String";

    void abuseVbc() {

        synchronized(ref) {           // OK
            synchronized (this) {     // WARN
            }
        }
    }
}

final class AuxilliaryAbuseOfVbc {

    void abuseVbc(SomeVbc vbc) {

        synchronized(this) {           // OK
            synchronized (vbc) {       // WARN
            }
        }
    }
}

JEP 392: Packaging Tool

jpackage在JDK14引入,JDK15作为incubating工具,在JDK16转正,从jdk.incubator.jpackage转为jdk.jpackage。它支持Linux: deb and rpm、macOS: pkg and dmg、Windows: msi and exe

JEP 393: Foreign-Memory Access API (Third Incubator)

Foreign-Memory Access API在JDK14首次引入作为incubating API,在JDK15处于第二轮incubating,在JDK16处于第三轮incubating

JEP 394: Pattern Matching for instanceof

instanceof的模式匹配在JDK14作为preview,在JDK15作为第二轮的preview,在JDK16转正

JEP 395: Records

Record类型在JDK14作为preview,在JDK15处于第二轮preview,在JDK16转正

JEP 396: Strongly Encapsulate JDK Internals by Default

对内部的api进行更多的封装,鼓励开发者从使用内部的方法迁移到标准的API,但是sun.misc.Unsafe还是继续保留

JEP 397: Sealed Classes (Second Preview)

Sealed Classes在JDK15作为preview引入,在JDK16作为第二轮preview

细项解读

上面列出的是大方面的特性,除此之外还有一些api的更新及废弃,主要见JDK 16 Release Notes,这里举几个例子。

添加项

  • Add InvocationHandler::invokeDefault Method for Proxy''s Default Method Support (JDK-8159746)

给InvocationHandler添加invokeDefault方法

  • Day Period Support Added to java.time Formats (JDK-8247781)

java.time支持Day Period

  • Add Stream.toList() Method (JDK-8180352) (JDK-8180352)

Stream新增toList方法

  • Concurrently Uncommit Memory in G1 (JDK-8236926)

针对G1提供了并发归还内存给操作系统

移除项

  • Removal of Experimental Features AOT and Graal JIT (JDK-8255616)

移除jaotc工具

  • Deprecated Tracing Flags Are Obsolete and Must Be Replaced With Unified Logging Equivalents (JDK-8256718)

使用-Xlog:class+load=info替代-XX:+TraceClassLoading;使用-Xlog:class+unload=info替代-XX:+TraceClassUnloading;使用-Xlog:exceptions=info替代-XX:+TraceExceptions

废弃项

  • Terminally Deprecated ThreadGroup stop, destroy, isDestroyed, setDaemon and isDaemon (JDK-8256643)

废弃ThreadGroup的stop, destroy, isDestroyed, setDaemon, isDaemon方法

  • Deprecated the java.security.cert APIs That Represent DNs as Principal or String Objects (JDK-8241003)

废弃了java.security.cert.X509Certificate的getIssuerDN()、getSubjectDN()方法

已知问题

  • Incomplete Support for Unix Domain Sockets in Windows 2019 Server (JDK-8259014)

Unix Domain Sockets对Windows 2019 Server的支持还不完善

  • TreeMap.computeIfAbsent Mishandles Existing Entries Whose Values Are null (JDK-8259622)

TreeMap.computeIfAbsent方法针对null的处理与规范有偏差

其他事项

  • Enhanced Support of Proxy Class (JDK-8236862)

对Proxy Class进行了增强,支持jdk.serialProxyInterfaceLimit属性

  • Support Supplementary Characters in String Case Insensitive Operations (JDK-8248655)

对compareToIgnoreCase、equalsIgnoreCase、regionMatches方法的Case Insensitive语义进行了增强

  • HttpClient.newHttpClient and HttpClient.Builder.build Might Throw UncheckedIOException (JDK-8248006)

HttpClient.newHttpClient及HttpClient.Builder.build方法可能抛出UncheckedIOException

  • The Default HttpClient Implementation Returns Cancelable Futures (JDK-8245462)

默认的HttpClient实现返回Cancelable Futures

小结

Java16主要有如下几个特性

  • JEP 338: Vector API (Incubator)
  • JEP 347: Enable C++14 Language Features
  • JEP 357: Migrate from Mercurial to Git
  • JEP 369: Migrate to GitHub
  • JEP 376: ZGC: Concurrent Thread-Stack Processing
  • JEP 380: Unix-Domain Socket Channels
  • JEP 386: Alpine Linux Port
  • JEP 387: Elastic Metaspace
  • JEP 388: Windows/AArch64 Port
  • JEP 389: Foreign Linker API (Incubator)
  • JEP 390: Warnings for Value-Based Classes
  • JEP 392: Packaging Tool
  • JEP 393: Foreign-Memory Access API (Third Incubator)
  • JEP 394: Pattern Matching for instanceof
  • JEP 395: Records
  • JEP 396: Strongly Encapsulate JDK Internals by Default
  • JEP 397: Sealed Classes (Second Preview)

doc

  • JDK 16 Features
  • JDK 16 Release Notes
  • Oracle JDK 16 Release Notes
  • Java SE deprecated-list
  • The Arrival of Java 16
  • Java 16 and IntelliJ IDEA

关于React 16 的新特性react16.8新特性的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于03、React系列之--ES6的新特性、30分钟精通React今年最劲爆的新特性——React Hooks、C++的新特性for-each、Java16的新特性等相关知识的信息别忘了在本站进行查找喔。

本文标签: