useEffect与useLayoutEffect

59次阅读

共计 3769 个字符,预计需要花费 10 分钟才能阅读完成。

React Hook 让无狀态组件拥有了许多只有有狀态组件的能力,如自更新能力(setState,使用 useState),访问 ref(使用 useRef 或 useImperativeMethods),访问 context(使用 useContext),使用更高级的 setState 设置(useReducer),及进行类似生命周期的阶段性方法(useEffect 或 useLayoutEffect)。
当然还有一些 Hook,带来了一些新功能,如 useCallback,这是对事件句柄进行缓存,useState 的第二个返回值是 dispatch,但是每次都是返回新的,使用 useCallback,可以让它使用上次的函数。在虚拟 DOM 更新过程中,如果事件句柄相同,那么就不用每次都进行 removeEventListner 与 addEventListner。最后就是 useMemo,取得上次缓存的数据,它可以说是 useCallback 的另一种形式。
useState:setState useReducer:setState useRef: refuseImperativeMethods: ref useContext: context useCallback: 可以对 setState 的优化 useMemo: useCallback 的变形 useLayoutEffect: 类似 componentDidMount/Update, componentWillUnmount useEffect: 类似于 setState(state, cb)中的 cb,总是在整个更新周期的最后才执行
从上面的描述来看 useEffect 的时期是非常晚,可以保证页面是稳定下来再做事情。但是 useEffect 与 useLayoutEffect 与有狀态组件的生命周期钩子又有一点不一样。
useEffect(function(){
//dosomething
return function(){
//dosomething
}
}, inputs)

useEffect 与 useLayoutEffect 的第一个参数是一个函数(初始函数),这函数还会返回另一个清理用的函数(清理函数,在官方文档中没有明确的文字,都注释使用了 clean up 的字眼,就姑且这样叫)。当某个无狀态组件要在某个阶段执行这些钩子,它会优先执行清理函数再执行初始函数。
<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″>
<meta name=”viewport” content=”width=device-width”>

<script src=”./react.development.js”></script>
<script src=”./react-dom.development.js”></script>
<script type=’text/javascript’ src=”./lib/babel.js”></script>
</head>
<body>

<div id=’root’ class=”root”>
</div>
<script type=’text/babel’>

var container = document.getElementById(‘root’);
var {useState, useEffect, useLayoutEffect} = React;
function Example() {
const [count, setCount] = useState(0);
const [text, setText] = useState(”);
var a = useRef(“xxx”)
useEffect(() => {// 初始函数
console.log(a, ‘useEffect’)
document.title = `You clicked ${count} times`;
return () =>{// 清理函数
console.log(a, ‘end useEffect’)
document.title = `remove`;
}
});
useLayoutEffect(() => {// 初始函数
console.log(a, ‘useLayoutEffect’)
document.title = `You clicked ${count} times`;
return () =>{// 清理函数
console.log(a, ‘end useLayoutEffect’)
document.title += ‘!!!’;
}
});
console.log(count, ‘ 更新 Example’)
return (
<quoteblock>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<input ref={a} value={text} onChange={function(e){
setText(e.target.value)
}} />
<span> 共 {text.length} 个字符 </span><Child />
</quoteblock>
);
}

class App extends React.Component{
state = {
aaa: 1
}
onClick(){
this.setState(function(s){
return {
aaa: s.aaa +1
}
})
}
componentDidMount(){
console.log(“app mount”)
}
componentDidUpdate(){
console.log(“app update”)
}
render(){
return <div>{this.state.aaa < 10 ? <Example />: null}
<h1 onClick={this.onClick.bind(this)}>{this.state.aaa}</h1>
</div>
}
}

class Child extends React.Component {
componentDidMount(){
console.log(“Child mount”)
}
componentDidUpdate(){
console.log(“Child update”)
}
render(){
return <span>Child</span>
}
}
ReactDOM.render(<App />, container)

</script>
</html>

初次渲染的界面与日志
如果我们向 input 输入内容,就会发现它每次都会先进行 useEffect 与 useLayout 的清理函数,再执行他们的初始函数。并且发现 useEffect 的函数会在最后才执行,它会晚于包含它的父函数。我们可以点击页面上的 h1 标签,就可以证明这一点。

点击 h1 会不断递增数字,到 10 时会销供 Example 这个无狀态组件与它的子组件 Child。下面是数字到 10 时的界面与日志。

在我的迷你 React 框架中是这样实现这两个钩子
export function useEffect(create, inputs) {
return dispatcher.useEffect(create, inputs, PASSIVE, ‘passive’, ‘unpassive’);
}
export function useLayoutEffect(create, inputs) {
return dispatcher.useEffect(create, inputs, HOOK, ‘layout’, ‘unlayout’);
}
export var dispatcher = {
// 略 …
useEffect(create, inputs, EffectTag, createList, destoryList) {
let fiber = getCurrentFiber();
let cb = dispatcher.useCallbackOrMemo(create, inputs);
if (fiber.effectTag % EffectTag) {
fiber.effectTag *= EffectTag;
}
let updateQueue = fiber.updateQueue;
let list = updateQueue[createList] || (updateQueue[createList] = []);
updateQueue[destoryList] || (updateQueue[destoryList] = []);
list.push(cb);
},
// 略 …
};

它们就是执行时机不一样。
当目前 React Hook 还是实验性质,不排除会改变。目前有 9 种钩子,其实之前有十种,useMutationEffect 前不久已经完蛋了。useMemo 与 useCallback 很相近,但觉得 useMemo 的使用场合很少,不知它会不会废掉。useEffect 不好用,不像 useLayoutEffect 那么明显可以与有狀态组件的生命周期钩子相对应。useImperativeMethods 这个名字起得不好,可能以后也会调整。当然这只是我的看法。
React Hook 是一个很棒的设计,它其实是将有狀态组件的更新机制 (setState/forceUpdate) 的内部实现进行了更广泛的应用。当它的 API 稳定下来我会与大家分享它们更深层次的实现。

正文完
 0