前言node
在React 16.7 的版本中,Hooks 诞生了,截止到目前, 也有五六个月了, 想必你们也也慢慢熟悉了这个新名词。 react
我也同样, 对着这个新特性充满了好奇, 也写了几个demo 体验一下, 这个特性使得咱们能够在一个函数组件中实现管理状态, 能够说是十分的神奇。 楼主最近也看了一些这方面的文章, 在这里总结分享一下, 但愿对你们有所启发。git
首先, 咱们须要知道的是, 只有在 React scope 内调用的 Hooks 才是有效的,那 React 用什么机制来保证 Hooks 是在正确的上下文被调用的呢?github
dispatcher 是一个包含了诸多 Hook functions 的共享对象,在 render phase,它会被自动的分配或者销毁,它也保证 Hooks 不会在React component 以外被调用。数组
Hooks 功能的开启和关闭由一个flag 控制,这意味着, 在运行时之中, 能够动态的开启,关闭 Hooks相关功能。ide
React 16.6.X 也有一些试验性的功能是经过这种方式控制的, 具体实现参考:函数
对应源码spa
if (enableHooks) { ReactCurrentOwner.currentDispatcher = Dispatcher; } else { ReactCurrentOwner.currentDispatcher = DispatcherWithoutHooks; }
render 执行完毕以后,就销毁dispatcher, 这样也能组织在 react 渲染周期以外意外的调用Hooks.3d
对应源码:code
// We're done performing work. Time to clean up. isWorking = false; ReactCurrentOwner.currentDispatcher = null; resetContextDependences(); resetHooks(); // Yield back to main thread.
Hooks 的执行是由一个叫resolveDispatcher
的函数来决定的。 就像以前提到的, 在React 渲染周期以外 调用Hooks 是无效的, 这时候, React 也会跑出错误:
'Hooks can only be called inside the body of a function component.'
源码以下:
function resolveDispatcher() { const dispatcher = ReactCurrentOwner.currentDispatcher; invariant( dispatcher !== null, 'Hooks can only be called inside the body of a function component.', ); return dispatcher; }
以上咱们了解了Hooks的基础机制, 下面咱们再看几个核心概念。
咱们都知道, Hooks 的调用顺序十分重要。
React 假设当你屡次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的。
Hooks 不是独立的,就比如是根据调用顺序被串起来的一系列结点。
在了解这个机制以前,咱们须要了解几个概念:
用一个例子来解释吧, 假设, 咱们有一个状态集:
{ foo: 'foo', bar: 'bar', baz: 'baz', }
处理Hooks的时候,会被处理成一个队列, 每个结点都是一个 state 的 model :
{ memoizedState: 'foo', next: { memoizedState: 'bar', next: { memoizedState: 'bar', next: null } } }
此处源码:
function createHook(): Hook { return { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; }
在一个function Component 被渲染以前, 一个名为 prepareHooks
的方法会被调用, 在这个方法里, 当前的Fiber 和 Hooks 队列重的第一个结点会被储存到一个全局变量里, 这样, 下次调用 useXXX
的时候, React 就知道改运行哪一个context了。
对应源码:
let currentlyRenderingFiber let workInProgressQueue let currentHook // Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:123 function prepareHooks(recentFiber) { currentlyRenderingFiber = workInProgressFiber currentHook = recentFiber.memoizedState } // Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:148 function finishHooks() { currentlyRenderingFiber.memoizedState = workInProgressHook currentlyRenderingFiber = null workInProgressHook = null currentHook = null } // Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:115 function resolveCurrentlyRenderingFiber() { if (currentlyRenderingFiber) return currentlyRenderingFiber throw Error("Hooks can't be called") } // Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:267 function createWorkInProgressHook() { workInProgressHook = currentHook ? cloneHook(currentHook) : createNewHook() currentHook = currentHook.next workInProgressHook } function useXXX() { const fiber = resolveCurrentlyRenderingFiber() const hook = createWorkInProgressHook() // ... } function updateFunctionComponent(recentFiber, workInProgressFiber, Component, props) { prepareHooks(recentFiber, workInProgressFiber) Component(props) finishHooks() }
更新结束后, 一个名为 finishHooks
的方法会被调用, Hooks 队列中第一个结点的引用会被记录在 memoizedState
变量里, 这个变量是全局的, 意味着能够在外部去访问, 好比:
const ChildComponent = () => { useState('foo') useState('bar') useState('baz') return null } const ParentComponent = () => { const childFiberRef = useRef() useEffect(() => { let hookNode = childFiberRef.current.memoizedState assert(hookNode.memoizedState, 'foo') hookNode = hooksNode.next assert(hookNode.memoizedState, 'bar') hookNode = hooksNode.next assert(hookNode.memoizedState, 'baz') }) return ( <ChildComponent ref={childFiberRef} /> ) }
下面咱们就拿最多见的Hook来具体分析。
好比:
const [count, setCount] = useState(0);
其实, useState
的背后,是 useReducer
, 它提供一个一个简单的预先定义的 reducer handler
。 源码实现
也就意味着, 咱们经过 useState拿到的两个值, 其实分别是一个 reducer 的 state
, 和 一个 action 的 dispatcher
.
此处源码:
function basicStateReducer(state, action) { return typeof action === 'function' ? action(state) : action; }
如代码所示, 咱们能够直接提供一个 state 和对应的 action dispatcher。 可是与此同时, 咱们也能够直接传递一个包含action 的dispatcher 进去, 接收一个旧的state, 返回新的state.
这意味着咱们能够把一个state的setter看成一个参数传递给Component, 而后在父组件里修改state, 而不用传递一个新的prop进去。
简单示例:
const ParentComponent = () => { const [name, setName] = useState() return ( <ChildComponent toUpperCase={setName} /> ) } const ChildComponent = (props) => { useEffect(() => { props.toUpperCase((state) => state.toUpperCase()) }, [true]) return null }
官网中也有相似的例子:
function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </> ); }
说完了State, 咱们再看一下Effect。
Efftect 稍微有些不一样, 它增长了额外的逻辑层。 在深刻具体的实现以前, 咱们须要事先了解几点概念:
render
的时候被建立, 在 painting
以后被执行, 在下一次painting
以前被销毁。须要注意的一点是, painting
和 render
仍是有所区别的,render method 只是建立了一个Fiber node, 还没开始 paint.
// 脑坑疼, 休息一下再补充,未完待续...