React16新特性解读
这两天逛推特的时候偶然关注了一下某前端大佬,进而发现了react团队的Dan的推特(我的微信头像,帅的一批),又进而发现react16更新了新的特性(表示已经很久没有关注过前端的技术趋势了),感觉新特性还是比较有意思的,所以写个blog来记录下,以下内容均为官网内容的整理和个人的总结,如有不对的地方欢迎指正
。
这里分两块分析
Hooks
这次的新特性是Hooks(钩子)。什么是钩子函数,我的理解是触发某种条件就会触发的函数。
钩子函数的运用其实是很广泛的,react中的生命周期函数其实都是钩子函数。这次的新特性中Hooks分了两种,一种叫做Basic Hooks基本钩子函数,另一种叫做Additional Hooks额外钩子函数,Hooks只能用在函数式组件中,而不能用在类组件中
。
Basic Hooks
有以下几种:
- useState
- useEffect
- useContext
useState
useState这个钩子取代了之前的setState方法以及对this.state的定义,这意味着你将不再需要在constructor里写this.state和使用setState方法
。
另外官方文档中说道你可以多次使用这些钩子函数,也就是说一个function里面你可以写多个useState和useEffect
1 | function Counter() { |
useEffect
useEffect这个钩子是生命周期函数ComponentDidMount和ComponentDidUpdate的替代,在这个钩子中你可以写对state的操作。
官方说只要是render之后都会走这个钩子。
1 | function Example() { |
这段代码的官方解释如下:1
2
3
4
5
6We declare the count state variable, and then we tell React we need to use an effect.
We pass a function to the useEffect Hook. This function we pass is our effect.
Inside our effect, we set the document title using the document.title browser API.
We can read the latest count inside the effect because it’s in the scope of our function.
When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM.
This happens for every render, including the first one.
下面是详细对比:
1 | //这段代码表示的是获取一个朋友登录状态,是否在线 |
1 | function FriendStatusWithCounter(props) { |
官方的思想是利用钩子函数应该是让我们把代码分割成一个个的doing块,而不是按照生命周期来分割代码,组件的effects应该按照代码顺序来执行(可能表达的不是很对)。
原文如下:1
2Hooks lets us split the code based on what it is doing rather than a lifecycle method name.
React will apply every effect used by the component, in the order they were specified.
再来看componentDidUpdate方法,它其实有两个默认参数prevProps和prevState,用来表示前一个props和state。所以useEffect也同样有这么个东西,即第二个参数(array类型)。
官方代码如下:1
2
3
4
5
6
7
8
9
10
11//原来的写法
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
//useEffect的写法
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
解释一下这个[count]
,当渲染组件的时候第一次进入useEffect的时候会将你传入的第二个参数进行保存,当下次进来的时候useEffect会去判断这次的count和上次的count时候不同,从而起到原本componentDidUpdate的效果。
官方解释如下:1
2
3
4
5
6
7
8
9In the example above, we pass [count] as the second argument.
What does this mean? If the count is 5, and then our component re-renders with count still equal to 5,
React will compare [5] from the previous render and [5] from the next render.
Because all items in the array are the same (5 === 5), React would skip the effect.
That’s our optimization.
When we render with count updated to 6, React will compare the items in the [5] array from the previous
render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6.
If there are multiple items in the array, React will re-run the effect even if just one of them is different.
useContext
官方文档对这个钩子函数的描述比较少,可以看下官方的解释。
1 | const context = useContext(Context); |
这个钩子函数会接收一个React.createContext返回的Context对象,并且将当前最近的provider的context的值返回,当provider更新时,这个钩子函数将会触发最新的context进行重绘
这里补充下React.createContext这个16版本的新特性。
因为React是单向数据流,所以数据的传递都是从上之下传递的,当我父节点的值想要传递个孙节点的时候一般来讲要通过子节点,
如下图
图是老图,图中方法名不是新特性的方法名,这里只是讲个概念
所以有了context(上下文)这个东西,
如下图
图是老图,图中方法名不是新特性的方法名,这里只是讲个概念
React.createContext会返回一个包含Provider和Consumer这两个组件的对象,在Provider组件中设置的value将能够在Consumer组件中使用,也就是说你在父组件外包一层Provider组件,将父组件的值传给Provider的value,将孙组件外层包Consumer组件,孙组件就可以拿到父组件的值。这里要注意的是Consumer接收的是一个函数对象。
代码如下:
1 | let {Provider,Consumer} = React.createContext(); |
现在回到useContext,很明确了useContext可以让你直接拿到最近的Provider的value值,作为context用在你当前的组件里,不需要再去写Consumer这个组件。
Additional Hooks
有以下几种:
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeMethods
- useMutationEffect
- useLayoutEffect
这里就不全都介绍了,介绍几个我认为比较重要的钩子函数。
useReducer
这个比较重要,是数据传递方面的钩子函数,将在后面的与redux的运用
写一起。
useMemo
官方解释如下:1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
这个钩子函数官方讲的也很简单,并没有具体的例子,这个函数会返回一个memoized的value,它有两个入参,分别是”create” function和一个array类型的参数,array将作为输入传给useMemo,useMemo将会保存这个值,当array中的值发生变化时才会去走”create” function这个函数方法从而更新memoizedValue,这样就避免了rerender过程中昂贵的计算。
个人demo如下,因为没有官方例子,所以自己写了一个:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import React, { useState, useEffect, useMemo } from 'react'
function Toggle () {
const step = 2;
const [count, setCount] = useState(0)
const changeCount = (count,step)=>{
console.log("ccccccccccc\n",count)
return function(){
return count+step
};
}
let memoizedValue = useMemo(()=>{
changeCount(count,step)
},[count,step])
//很遗憾这个memoizedValue没有打印出值,所以上述内容中的create打了引号,事实上官方也是打引号的,但官方没有解释这个create是什么函数方法
console.log(memoizedValue)
return (
<div>
<br/>
<p>Memo</p>
<span>{memoizedValue}</span>
<button onClick={() => setCount(count===3?count:count+1)}>//每次点击改变count的值,count改变后会去触发useMemo
click
</button>
</div>
)
}
export default Toggle
上述例子很简单就是点击改变count的值从而去触发useMemo,当count为3的时候count的值就不变了,useMemo就不会去执行changeCount这个方法,这个方法就是上述的create function
useCallback
这个钩子函数和useMemo一样,有两个入参,一个是callBack方法,一个是array类型的inputs,也是只有当inputs中的值改变时才回去执行callBack方法,官方例子如下:
1 | const memoizedCallback = useCallback( |
同样它返回一个memoized类型的Callback(日了,这个memoized到底是什么),同样目的也是为了防止rerender的时候走这些方法函数,这个useCallback有个好处就是你可以将对于子组件的操作放callBack里,起到一个类似生命周期函数shouldComponentUpdate的功能。
这个demo没写,因为与useMemo功能类似就不搞了(不想写,好烦,要写子组件和孙组件)。
useRef
作用和之前this.refs是一样的,官方demo如下,就不赘述了:
1 | function TextInputWithFocusButton() { |
关于新特性的hooks就这么多了,没讲到的是我觉得写代码的时候不经常用的,其实我ref都不想写,因为本来就不推荐用这个东西。
与redux的运用(useReducer)
我觉得新特性的hooks与redux的运用才是重点,然而当前官方文档的重心是在useState和useEffect上,因为官方花了大篇幅来讲解useEffect和useState。
现阶段我也只是摸索阶段,可能有讲的不对的地方。
useReducer
新特性中有个useReducer,这个让熟悉redux的人眼前一亮,难道不需要第三方库就能解决react难受的数据的传递问题了么。
先来看下官方的useReducer的例子:
1 | const initialState = {count: 0}; |
可以看出这个demo和我们写redux的时候十分相似,不,应该说简直一毛一样。
这里我就不赘述redux了,直接讲useReducer。
从上面的例子可以看出,useReducer接收了两个参数,reducer和initialSate,并且返回了对应的一个数组将值解构给了state和dispatch,下面通过按钮操作了state的值进行对应的变化。
这里可以看下useReducer的源码:1
2
3
4
5
6
7
8
9
10
11
12
13var ReactCurrentOwner = {
curren:null,
currentDispatch:null
}
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
function useReducer(reducer, initialState, initialAction) {
var dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialState, initialAction);
}
惊呆了,userReducer只有四行代码,但是追溯下去发现有问题啊,dispacher哪来的useReducer,其实官方文档里说了,上面的userReducer的代码只是一个简版(react.development.js中),真正的useReducer代码如下(react-dom.development.js中),很复杂的一段代码,这里就不讲了,太复杂(毕竟我是菜鸡,Dan的那种层次还达不到)。通过打印useReducer的返回值,可以看到,返回数组的第一个元素是state,第二个是一个function dispatchAction,这和redux十分相似。
再来看下上面的demo的代码结构,结合redux的写法,可以发现demo中其实是把reducer和action写在一起了,这其实是和redux的理念冲突的,因为redux提倡在reducer中不做不纯净的操作,希望将赋值和对数据的操作分开,因此,一般对于数据的操作都写在action中,reducer中只是取值赋值操作。
另外一个严厉的问题就是异步怎么写?
总结
新特性也是刚出来,还不是正式版,所以我也只是对于当前beta版本做一些解读,如有不对欢迎指正。
editBy AmamiRyoin