您好,登錄后才能下訂單哦!
本篇內容介紹了“React架構的演變之如何理解Hooks的實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Hook 如何與組件關聯
在之前的文章中多次提到,Fiber 架構下的 updateQueue、effectList 都是鏈表的數據結構,然后掛載的 Fiber 節點上。而一個函數組件內所有的 Hooks 也是通過鏈表的形式存儲的,最后掛載到 fiber.memoizedState 上。
function App() { const [num, updateNum] = useState(0) return <div onClick={() => updateNum(num => num + 1)} >{ num }</div> } export default App
我們先簡單看下,調用 useState 時,構造鏈表的過程:
var workInProgressHook = null var HooksDispatcherOnMount = { useState: function (initialState) { return mountState(initialState) } } function function mountState(initialState) { // 新的 Hook 節點 var hook = mountWorkInProgressHook() // 緩存初始值 hook.memoizedState = initialState // 構造更新隊列,類似于 fiber.updateQueue var queue = hook.queue = { pending: null, dispatch: null, lastRenderedState: initialState } // 用于派發更新 var dispatch = queue.dispatch = dispatchAction.bind( null, workInProgress, queue ) // [num, updateNum] = useState(0) return [hook.memoizedState, dispatch] } function mountWorkInProgressHook() { var hook = { memoizedState: null, baseState: null, baseQueue: null, queue: null, next: null } if (workInProgressHook === null) { // 構造鏈表頭節點 workInProgress.memoizedState = workInProgressHook = hook } else { // 如果鏈表已經存在,在掛載到 next workInProgressHook = workInProgressHook.next = hook } return workInProgressHook }
如果此時有兩個 Hook,第二個 Hook 就會掛載到第一個 Hook 的 next 屬性上。
function App() { const [num, updateNum] = useState(0) const [str, updateStr] = useState('value: ') return <div onClick={() => updateNum(num => num + 1)} >{ str } { num }</div> } export default App
Hook
Hook 的更新隊列
Hook 通過 .next 彼此相連,而每個 Hook 對象下,還有個 queue 字段,該字段和 Fiber 節點上的 updateQueue 一樣,是一個更新隊列在,上篇文章 《React 架構的演變-更新機制》中有講到,React Fiber 架構中,更新隊列通過鏈表結構進行存儲。
class App extends React.Component { state = { val: 0 } click () { for (let i = 0; i < 3; i++) { this.setState({ val: this.state.val + 1 }) } } render() { return <div onClick={() => { this.click() }}>val: { this.state.val }</div> } }
點擊 div 之后,產生的 3 次 setState 通過鏈表的形式掛載到 fiber.updateQueue 上,待到 MessageChannel 收到通知后,真正執行更新操作時,取出更新隊列,將計算結果更新到 fiber.memoizedState。
setState
而 hook.queue 的邏輯和 fiber.updateQueue 的邏輯也是完全一致的。
function App() { const [num, updateNum] = useState(0) return <div onClick={() => { // 連續更新 3 次 updateNum(num => num + 1) updateNum(num => num + 1) updateNum(num => num + 1) }} > { num } </div> } export default App; var dispatch = queue.dispatch = dispatchAction.bind( null, workInProgress, queue ) // [num, updateNum] = useState(0) return [hook.memoizedState, dispatch]
調用 useState 的時候,返回的數組第二個參數為 dispatch,而 dispatch 由 dispatchAction bind 后得到。
function dispatchAction(fiber, queue, action) { var update = { next: null, action: action, // 省略調度相關的參數... }; var pending = queue.pending if (pending === null) { update.next = update } else { update.next = pending.next pending.next = update } queue.pending = update // 執行更新 scheduleUpdateOnFiber() }
可以看到這里構造鏈表的方式與 fiber.updateQueue 如出一轍。之前我們通過 updateNum 對 num 連續更新了 3 次,最后形成的更新隊列如下:
更新隊列
函數組件的更新
前面的文章分享過,Fiber 架構下的更新流程分為遞(beginWork)、歸(completeWork)兩個步驟,在 beginWork 中,會依據組件類型進行 render 操作構造子組件。
function beginWork(current, workInProgress) { switch (workInProgress.tag) { // 其他類型組件代碼省略... case FunctionComponent: { // 這里的 type 就是函數組件的函數 // 例如,前面的 App 組件,type 就是 function App() {} var Component = workInProgress.type var resolvedProps = workInProgress.pendingProps // 組件更新 return updateFunctionComponent( current, workInProgress, Component, resolvedProps ) } } } function updateFunctionComponent( current, workInProgress, Component, nextProps ) { // 構造子組件 var nextChildren = renderWithHooks( current, workInProgress, Component, nextProps ) reconcileChildren(current, workInProgress, nextChildren) return workInProgress.child }
看名字就能看出來,renderWithHooks 方法就是構造帶 Hooks 的子組件。
function renderWithHooks( current, workInProgress, Component, props ) { if (current !== null && current.memoizedState !== null) { ReactCurrentDispatcher.current = HooksDispatcherOnUpdate } else { ReactCurrentDispatcher.current = HooksDispatcherOnMount } var children = Component(props) return children }
從上面的代碼可以看出,函數組件更新或者首次渲染時,本質就是將函數取出執行了一遍。不同的地方在于給 ReactCurrentDispatcher 進行了不同的賦值,而 ReactCurrentDispatcher 的值最終會影響 useState 調用不同的方法。
根據之前文章講過的雙緩存機制,current 存在的時候表示是更新操作,不存在的時候表示首次渲染。
function useState(initialState) { // 首次渲染時指向 HooksDispatcherOnMount // 更新操作時指向 HooksDispatcherOnUpdate var dispatcher = ReactCurrentDispatcher.current return dispatcher.useState(initialState) }
HooksDispatcherOnMount.useState 的代碼前面已經介紹過,這里不再著重介紹。
// HooksDispatcherOnMount 的代碼前面已經介紹過 var HooksDispatcherOnMount = { useState: function (initialState) { return mountState(initialState) } }
我們重點看看 HooksDispatcherOnMount.useState 的邏輯。
var HooksDispatcherOnUpdateInDEV = { useState: function (initialState) { return updateState() } } function updateState() { // 取出當前 hook workInProgressHook = nextWorkInProgressHook nextWorkInProgressHook = workInProgressHook.next var hook = nextWorkInProgressHook var queue = hook.queue var pendingQueue = queue.pending // 處理更新 var first = pendingQueue.next var state = hook.memoizedState var update = first do { var action = update.action state = typeof action === 'function' ? action(state) : action update = update.next; } while (update !== null && update !== first) hook.memoizedState = state var dispatch = queue.dispatch return [hook.memoizedState, dispatch] }
如果有看之前的 setState 的代碼,這里的邏輯其實是一樣的。將更新對象的 action 取出,如果是函數就執行,如果不是函數就直接對 state 進行替換操作。
“React架構的演變之如何理解Hooks的實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。