前言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.
// 腦坑疼, 休息一下再補充,未完待續...