内容简介:在上一篇文章中,我们谈到 Hooks 给 React 带来的一些在开发体验上的改变,如果你已经开始尝试 React Hooks,也许你会跟我一样碰到一个令人疑惑的地方,如果没有的话,那就再好不过啦,我就权当做个记录,以便他人之需。我们先以官方的例子开始:看到
在上一篇文章中,我们谈到 Hooks 给 React 带来的一些在开发体验上的改变,如果你已经开始尝试 React Hooks,也许你会跟我一样碰到一个令人疑惑的地方,如果没有的话,那就再好不过啦,我就权当做个记录,以便他人之需。
如何绑定事件?
我们先以官方的例子开始:
import React, { useState } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 复制代码
看到 onClick
绑定的那个匿名函数了吗?这样写的话,每次 render 的时候都会重新生成一个新的函数。这在之前可能不需要太在意,因为我们一般只是拿 Function Component 来实现一些展示型组件,在其之下不会有太多的子组件。但是如果我们拥抱 Hooks 之后,那么就不可控了。
虽然说在一般情况下,这并不会造成太大的性能问题,而且 Function Component 本身的性能就要比 Class Component 更好一点,但是难免会碰到需要优化的时候,比方说在重构原来的 Class Component 的时候,其中有个子组件是个 PureComponent
,便会使子组件的这个优化失效 ,那么怎么解决呢?
使用 useCallback
或 useMemo
来保存函数的引用,避免重复生成新的函数
function Counter() { const [count, setCount] = useState(0); const handleClick = useCallback(() => { setCount(count => count + 1) }, []); // 或者用useMemo // const handleClick = useMemo(() => () => {setCount(count => count + 1)}, []); return ( <div> <p>count: {count}</p> {/* Child为PureComponent */} <Child callback={handleClick} /> </div> ) } 复制代码
可见 useCallback(fn, inputs)
等同于 useMemo(() => fn, inputs)
,那么这两个 Hook 具体是怎么做到的呢?我们可以从源码中一窥究竟,我们以 useCallback
为例( useMemo
大体上都是一样的,就返回值不同,后面会提到)。
首先,在第一次执行 useCallback
时,React内部会调用 ReactFiberHooks
中的 mountCallback
,之后再次执行时调用的都是 updateCallback
,具体代码可以看这里: github.com/facebook/re…
我们一点点来看,先看下 mountCallback
:
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; hook.memoizedState = [callback, nextDeps]; return callback; } 复制代码
发现核心在于 mountWorkInProgressHook
这个方法
function mountWorkInProgressHook(): Hook { const hook: Hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list firstWorkInProgressHook = workInProgressHook = hook; } else { // Append to the end of the list workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; } 复制代码
代码比较简单,就不一一解释了,从上面的代码我们可以得知 Hooks 的本体:
const hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, } 复制代码
我们主要关注 memoizedState
和 next
, memoizedState
在不同的 Hook 中存放的值会有所不同,在 useCallback
中存的就是入参的值 [callback, deps]
, next
的值就是下一个 hook,也就是说 Hooks 其实就是一个单向链表,这也就解释了为什么 Hooks 需要在顶层调用,不能在循环、条件语句、嵌套函数中使用,因为需要保证每次调用的顺序一致。
再来看之后的 updateCallback
:
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T { // 这个hook就是第一次mount的hook const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; // 所以这里的memoizedState就是mount时候存着的[callback, deps] const prevState = hook.memoizedState; if (prevState !== null) { if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; // 比较两次的deps,相同的话就直接返回之前存的callback,而不是新传进来的callback if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } hook.memoizedState = [callback, nextDeps]; return callback; } 复制代码
useMemo
的实现与 useCallback
类似,大概看一下:
function mountMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; // 与useCallback不同的地方就是memoizedState中存的是nextCreate执行之后的结果 const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; // 返回执行结果 return nextValue; } function updateMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState !== null) { if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } // 这里也一样,存的是nextCreate执行之后的结果 const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; // 返回执行结果 return nextValue; } 复制代码
由以上代码便可以看出 useCallback
和 useMemo
在用法上的区别了。
除了这两个方法以外,还可以通过 context
来传递由 useReducer
生成的 dispatch
方法,来避免直接传递 callback
,因为 dispatch
是不变的。这个方法跟前面两种有本质上的区别,它从源头上就阻止了callback的传递,所以也就不会有前面提到的性能方面的顾虑,这也是官方推荐的方法,特别是组件树很大的情况下。所以上面的代码如果通过这种方式来写的话,就会是下面这样,有点像 Redux
:
import React, { useReducer, useContext } from 'react'; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; default: throw new Error(); } } const TodosDispatch = React.createContext(null); function Counter() { const [state, dispatch] = useReducer(reducer, {count: 0}); return ( <div> <p>count: {state.count}</p> <TodosDispatch.Provider value={dispatch}> <Child /> </TodosDispatch.Provider> </div> ) } function Child() { const dispatch = useContext(TodosDispatch); return ( <button onClick={() => dispatch({type: 'increment'})}> click </button> ) } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP Developer's Handbook
Chris Shiflett / Sams Publishing / 2003-3-29 / USD 39.99
The largest group with an unsatisfied demand for a good book on HTTP is the worldwide group of Web developers. A good book on HTTP can help new and old Web developers alike, as a thorough understandin......一起来看看 《HTTP Developer's Handbook》 这本书的介绍吧!