You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionresolveDispatcher(){constdispatcher=ReactCurrentDispatcher.current;invariant(dispatcher!==null,'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for'+' one of the following reasons:\n'+'1. You might have mismatching versions of React and the renderer (such as React DOM)\n'+'2. You might be breaking the Rules of Hooks\n'+'3. You might have more than one copy of React in the same app\n'+'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',);returndispatcher;}
functionmountWorkInProgressHook(): Hook{consthook: Hook={memoizedState: null,baseState: null,queue: null,baseUpdate: null,next: null,};if(workInProgressHook===null){// This is the first hook in the listfirstWorkInProgressHook=workInProgressHook=hook;}else{// Append to the end of the listworkInProgressHook=workInProgressHook.next=hook;}returnworkInProgressHook;}
functionupdateWorkInProgressHook(): Hook{if(nextWorkInProgressHook!==null){// There's already a work-in-progress. Reuse it.workInProgressHook=nextWorkInProgressHook;nextWorkInProgressHook=workInProgressHook.next;currentHook=nextCurrentHook;nextCurrentHook=currentHook!==null ? currentHook.next : null;}else{// Clone from the current hook.invariant(nextCurrentHook!==null,'Rendered more hooks than during the previous render.',);currentHook=nextCurrentHook;constnewHook: Hook={memoizedState: currentHook.memoizedState,baseState: currentHook.baseState,queue: currentHook.queue,baseUpdate: currentHook.baseUpdate,next: null,};if(workInProgressHook===null){// This is the first hook in the list.workInProgressHook=firstWorkInProgressHook=newHook;}else{// Append to the end of the list.workInProgressHook=workInProgressHook.next=newHook;}nextCurrentHook=currentHook.next;}returnworkInProgressHook;}
React Hooks 进阶
前言
上一篇简单地介绍了一下 React Hooks 的背景和 API 的使用,这一篇深入探索一下 React Hooks 的实践和原理。
React Hooks 实践
模拟 Class Component 的生命周期
有的时候还是需要根据不同的生命周期来处理一些逻辑,React Hooks 几乎可以模拟出全部的生命周期。
componentDidMount
使用 useEffect 来实现,如下:
useEffect 第二个参数传空数组时,表示只会在执行一次。
componentWillUnMount
同样可以使用 useEffect 来实现,如下:
componentDidUpdate
componentDidUpdate 生命周期在组件每次更新之后执行,除了初始化 render 的时候不执行,所以可以设置一个标志位来判断是否是第一次 render,使用 useEffect + useRef 配合就可以实现:
getDerivedStateFromProps
getDeriverdStateFromProps 是 react 新版本中用来替代 componentWillReceiveProps,它可以感知 props 的变化,从而更新组件内部的 state,用 hooks 模拟这个生命周期,可以这样实现:
shouldComponentUpdate
React 16.6 引入 React.memo,是用来控制 Function Component 的重新渲染的,类似于 Class Component 的 PureComponent,可以跳过 props 没有变化时的更新,为了支持更加灵活的 props 对比,它还提供了第二个函数参数 areEqual(prevProps, nextProps),和 shouldComponentUpdate 相反的是,当该函数返回 true 时表示不更新函数,返回 false 则重新更新,用法如下:
除了上面这种方法可以模拟 shouldComponentUpdate 之外,React Hooks 还提供一个 useMemo 用来控制子组件重新渲染的,举一个例子如下:
在上面的例子中,只有 Parent 组件中的 count state 更新了,Child 才会重新渲染,否则不会。
React Hooks 原理
还记得我们之前讲过的使用 React Hooks 的两条规则吗?
现在我们来一一剖析一下为什么会有这个限制?
只能在 React 函数和自定义 Hooks 中使用
翻到 ReactHooks 对应的源码,贴出 Hooks 的定义如下:
所有的 Hooks 基本都调用了这个 resolveDispatcher(),定位到 resolveDispatcher,代码如下:
如果 ReactCurrentDispatcher.current 是空的,就会得出我们使用 Hooks 的方式不对,只有在 React 环境中才会给 ReactCurrentDispatcher 的 current 赋值,所以就可以解这个问题。
不在循环、条件或者嵌套函数中调用 Hook
为什么不能在循环、条件或者嵌套函数中调用 Hook,我们还是从源码出发寻找原因:
Hooks 的实现源码在 ReactFiberHooks.js。
在这个文件中,定义了 firstWorkInProgressHook 和 workInProgressHook 这两个全局变量,观察所有的 Hooks 实现,发现都执行了 const hook = mountWorkInProgressHook(),首先来看一下这个函数的实现:
我们来模拟一下定义多个 Hooks 时的流程:
这种结构就是一个链表结构,而每一个 Hook 的结构如下:
其中 memoizedState 存储当前 Hook 的结果,next 则连接到下一个 Hook,从而将所有 Hook 进行串联起来。这个链表结果存储在 Fiber 对象的 memoizedState 属性中,在 React 中,每个节点都对应一个 Fiber 对象,而 Fiber 的 memoizedState 用来存储该节点在上次渲染中的 state,这个属性是 Class Component 用来存储节点的 state 的,这也就是为什么 Hook 可以拥有 Class Component 功能的原因。
链表结构用图形显示如下:
在第二次渲染时,也就是 update 的时候,此时调用的是 Hook 对应的 update 方法,而 update 方法又分别执行了 updateWorkInProgressHook(),先来看看这个方法的实现:
在这个方法中,它会获取渲染时生成的 Hooks,并获取当前 update 的是处于链表的哪个节点,然后返回。
假如在条件语句中使用 Hook,如下:
初始渲染时,拿到的是 state1 => hook1,state2 => hook2,state3 => hook3,再次渲染时,condition 条件不满足,那么执行 state3 时拿到的就是 hook2,那整个逻辑就乱套了...
结语
React Hooks 解决了一部分问题,但同时自身也有一定的缺陷,比如要遵守一定规则、组件嵌套层次不明显导致 bug 定位难。所以在实际的开发实践中,还是要评估再选型。
The text was updated successfully, but these errors were encountered: