日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

usestate中的回调函数_React Hooks 源码解析(3):useState

發(fā)布時(shí)間:2025/3/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 usestate中的回调函数_React Hooks 源码解析(3):useState 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  • React 源碼版本: v16.11.0
  • 源碼注釋筆記:
airingursb/react?github.com

在寫(xiě)本文之前,事先閱讀了網(wǎng)上了一些文章,關(guān)于 Hooks 的源碼解析要么過(guò)于淺顯、要么就不細(xì)致,所以本文著重講解源碼,由淺入深,爭(zhēng)取一行代碼也不放過(guò)。那本系列講解第一個(gè) Hooks 便是 useState,我們將從 useState 的用法開(kāi)始,再闡述規(guī)則、講解原理,再簡(jiǎn)單實(shí)現(xiàn),最后源碼解析。另外,在本篇開(kāi)頭,再補(bǔ)充一個(gè) Hooks 的概述,前兩篇限于篇幅問(wèn)題一直沒(méi)有寫(xiě)一塊。

注:距離上篇文章已經(jīng)過(guò)去了兩個(gè)月,這兩個(gè)月業(yè)務(wù)繁忙所以沒(méi)有什么時(shí)間更新該系列的文章,但 react 這兩個(gè)月卻從 16.9 更新到了 16.11,review 了一下這幾次的更新都未涉及到 hooks,所以我也直接把源碼筆記這塊更新到了 16.11。

1. React Hooks 概述

Hook 是 React 16.8 的新增特性,它可以讓你在不編寫(xiě) class 的情況下使用 state 以及其他的 React 特性。其本質(zhì)上就是一類特殊的函數(shù),它們約定以 use 開(kāi)頭,可以為 Function Component 注入一些功能,賦予 Function Component 一些 Class Component 所具備的能力。

例如,原本我們說(shuō) Function Component 無(wú)法保存狀態(tài),所以我們經(jīng)常說(shuō) Stateless Function Component,但是現(xiàn)在我們借助 useState 這個(gè) hook 就可以讓 Function Component 像 Class Component 一樣具有狀態(tài)。前段時(shí)間 @types/react 也將 SFC 改成了 FC。

1.1 動(dòng)機(jī)

在 React 官網(wǎng)的 Hook 簡(jiǎn)介中列舉了推出 Hook 的原因:

  • 在組件之間復(fù)用狀態(tài)邏輯很難
  • 復(fù)雜組件變得難以理解
  • 難以理解的 class
  • 一,組件之間復(fù)用狀態(tài)邏輯很難。是我們系列第二篇中一直討論的問(wèn)題,此處不再贅述。

    二,復(fù)雜組件變得難以理解,即組件邏輯復(fù)雜。主要是針對(duì) Class Component 來(lái)說(shuō),我們經(jīng)常要在組件的各種生命周期中編寫(xiě)代碼,如在 componentDidMount 和 componentDidUpdate 中獲取數(shù)據(jù),但是在 componentDidMount 中可能也包括很多其他的邏輯,使得組件越開(kāi)發(fā)越臃腫,且邏輯明顯扎堆在各種生命周期函數(shù)中,使得 React 開(kāi)發(fā)成為了“面向生命周期編程”。而 Hooks 的出現(xiàn),將這種這種“面向生命周期編程”變成了“面向業(yè)務(wù)邏輯編程”,使得開(kāi)發(fā)者不用再去關(guān)心本不該關(guān)心的生命周期。

    三,難以理解的 class,表現(xiàn)為函數(shù)式編程比 OOP 更加簡(jiǎn)單。那么再深入一些去考慮性能,Hook 會(huì)因?yàn)樵阡秩緯r(shí)創(chuàng)建函數(shù)而變慢嗎?答案是不會(huì),在現(xiàn)在瀏覽器中閉包和類的原始性能只有在極端場(chǎng)景下又有有明顯的區(qū)別。反而,我們可以認(rèn)為 Hook 的設(shè)計(jì)在某些方面會(huì)更加高效:

  • Hook 避免了 class 需要的額外開(kāi)支,像是創(chuàng)建類實(shí)例和在構(gòu)造函數(shù)中綁定事件處理器的成本。
  • 符合語(yǔ)言習(xí)慣的代碼在使用 Hook 時(shí)不需要很深的組件樹(shù)嵌套。這個(gè)現(xiàn)象在使用高階組件、render props、和 context 的代碼庫(kù)中非常普遍。組件樹(shù)小了,React 的工作量也隨之減少。
  • 其實(shí),React Hooks 帶來(lái)的好處不僅是更函數(shù)式、更新粒度更細(xì)、代碼更清晰,還有以下三個(gè)優(yōu)點(diǎn):

  • 多個(gè)狀態(tài)不會(huì)產(chǎn)生嵌套,寫(xiě)法還是平鋪的:如 async/await 之于 callback hell 一樣,hooks 也解決了高階組件的嵌套地獄問(wèn)題。雖然 renderProps 也可以通過(guò) compose 解決這個(gè)問(wèn)題,但使用略為繁瑣,而且因?yàn)閺?qiáng)制封裝一個(gè)新對(duì)象而增加了實(shí)體數(shù)量。
  • Hooks 可以引用其他 Hooks,自定義 Hooks 更加靈活。
  • 更容易將組件的 UI 與狀態(tài)分離。
  • 1.2 Hooks API

    • useState
    • useEffect
    • useContext
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue
    • useResponder

    以上 Hooks API 都會(huì)在未來(lái)一一講解,此處不再贅述。本文先講解 useState。

    1.3 自定義 Hooks

    通過(guò)自定義 Hook,可以將組件邏輯提取到可重用的函數(shù)中。這里安利一個(gè)網(wǎng)站:https://usehooks.com/,里面收集了實(shí)用的自定義 Hooks,可以無(wú)縫接入項(xiàng)目中使用,充分體現(xiàn)了 Hooks 的可復(fù)用性之強(qiáng)、使用之簡(jiǎn)單。

    2. useState 的用法與規(guī)則

    import React, { useState } from 'react'const App: React.FC = () => {const [count, setCount] = useState<number>(0)const [name, setName] = useState<string>('airing')const [age, setAge] = useState<number>(18)return (<><p>You clicked {count} times</p><button onClick={() => {setCount(count + 1)setAge(age + 1)}}>Click me</button></>) }export default App

    如果用過(guò) redux 的話,這一幕一定非常眼熟。給定一個(gè)初始 state,然后通過(guò) dispatch 一個(gè) action,再經(jīng)由 reducer 改變 state,再返回新的 state,觸發(fā)組件重新渲染。

    它等價(jià)于下面這個(gè) Class Component:

    import React from 'react'class Example extends React.Component {constructor(props) {super(props);this.state = {count: 0,age: 18,name: 'airing'};}render() {return (<><p>You clicked {this.state.count} times</p><button onClick={() => this.setState({ count: this.state.count + 1,age: this.state.age + 1})}>Click me</button></>);} }

    可以看到 Function Component 比 Class Component 簡(jiǎn)潔需要,useState 的使用也非常簡(jiǎn)單。但需要注意的是,Hooks 的使用必須要符合這條規(guī)則:確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用。因此最好每次只在最頂層使用 Hook,不要在循環(huán)、條件、嵌套函數(shù)中調(diào)用 Hooks,否則容易出錯(cuò)。

    那么,為什么我們必須要滿足這條規(guī)則?接下來(lái),我們看一下 useState 的實(shí)現(xiàn)原理并自己親自動(dòng)手實(shí)現(xiàn)一個(gè) useState 便可一目了然。

    3. useState 的原理與簡(jiǎn)單實(shí)現(xiàn)

    3.1 Demo 1: dispatch

    第二節(jié)中我們發(fā)現(xiàn) useState 的用法蠻像 Redux 的,那我們基于 Redux 的思想,自己動(dòng)手實(shí)現(xiàn)一個(gè) useState:

    function useState(initialValue) {let state = initialValuefunction dispatch(newState) {state = newStaterender(<App />, document.getElementById('root'))}return [state, dispatch] }

    我們將從 React 中引入的 useState 替換成自己實(shí)現(xiàn)的:

    import React from 'react' import { render } from 'react-dom'function useState(initialValue: any) {let state = initialValuefunction dispatch(newState: any) {state = newStaterender(<App />, document.getElementById('root'))}return [state, dispatch] }const App: React.FC = () => {const [count, setCount] = useState(0)const [name, setName] = useState('airing')const [age, setAge] = useState(18)return (<><p>You clicked {count} times</p><p>Your age is {age}</p><p>Your name is {name}</p><button onClick={() => {setCount(count + 1)setAge(age + 1)}}>Click me</button></>) }export default App

    這個(gè)時(shí)候我們發(fā)現(xiàn)點(diǎn)擊按鈕不會(huì)有任何響應(yīng),count 和 age 都沒(méi)有變化。因?yàn)槲覀儗?shí)現(xiàn)的 useState 并不具備存儲(chǔ)功能,每次重新渲染上一次的 state 就重置了。這里想到可以在外部用個(gè)變量來(lái)存儲(chǔ)。

    3.2 Demo 2: 記憶 state

    基于此,我們優(yōu)化一下剛才實(shí)現(xiàn)的 useState:

    let _state: any function useState(initialValue: any) {_state = _state | initialValuefunction setState(newState: any) {_state = newStaterender(<App />, document.getElementById('root'))}return [_state, setState] }

    雖然按鈕點(diǎn)擊有變化了,但是效果不太對(duì)。如果我們刪掉 age 和 name 這兩個(gè) useState 會(huì)發(fā)現(xiàn)效果是正常的。這是因?yàn)槲覀冎挥昧藛蝹€(gè)變量去儲(chǔ)存,那自然只能存儲(chǔ)一個(gè) useState 的值。那我們想到可以用備忘錄,即一個(gè)數(shù)組,去儲(chǔ)存所有的 state,但同時(shí)我們需要維護(hù)好數(shù)組的索引。

    3.3 Demo 3: 備忘錄

    基于此,我們?cè)俅蝺?yōu)化一下剛才實(shí)現(xiàn)的 useState:

    let memoizedState: any[] = [] // hooks 的值存放在這個(gè)數(shù)組里 let cursor = 0 // 當(dāng)前 memoizedState 的索引function useState(initialValue: any) {memoizedState[cursor] = memoizedState[cursor] || initialValueconst currentCursor = cursorfunction setState(newState: any) {memoizedState[currentCursor] = newStatecursor = 0render(<App />, document.getElementById('root'))}return [memoizedState[cursor++], setState] // 返回當(dāng)前 state,并把 cursor 加 1 }

    我們點(diǎn)擊三次按鈕之后,打印出 memoizedState 的數(shù)據(jù)如下:

    打開(kāi)頁(yè)面初次渲染,每次 useState 執(zhí)行時(shí)都會(huì)將對(duì)應(yīng)的 setState 綁定到對(duì)應(yīng)索引的位置,然后將初始 state 存入 memoizedState 中。

    在點(diǎn)擊按鈕的時(shí)候,會(huì)觸發(fā) setCount 和 setAge,每個(gè) setState 都有其對(duì)應(yīng)索引的引用,因此觸發(fā)對(duì)應(yīng)的 setState 會(huì)改變對(duì)應(yīng)位置的 state 的值。

    這里是模擬實(shí)現(xiàn) useState,所以每次調(diào)用 setState 都有一次重新渲染的過(guò)程。

    重新渲染依舊是依次執(zhí)行 useState,但是 memoizedState 中已經(jīng)有了上一次是 state 值,因此初始化的值并不是傳入的初始值而是上一次的值。

    因此剛才在第二節(jié)中遺留問(wèn)題的答案就很明顯了,為什么 Hooks 需要確保 Hook 在每一次渲染中都按照同樣的順序被調(diào)用?因?yàn)?memoizedState 是按 Hooks 定義的順序來(lái)放置數(shù)據(jù)的,如果 Hooks 的順序變化,memoizedState 并不會(huì)感知到。因此最好每次只在最頂層使用 Hook,不要在循環(huán)、條件、嵌套函數(shù)中調(diào)用 Hooks。

    最后,我們來(lái)看看 React 中是怎樣實(shí)現(xiàn) useState 的。

    4. useState 源碼解析

    4.1 入口

    首先在入口文件 packages/react/src/React.js 中我們找到 useState,其源自 packages/react/src/ReactHooks.js。

    export function useState<S>(initialState: (() => S) | S) {const dispatcher = resolveDispatcher();return dispatcher.useState(initialState); }

    resolveDispatcher() 返回的是 ReactCurrentDispatcher.current,所以 useState 其實(shí)就是 ReactCurrentDispatcher.current.useState。

    那么,ReactCurrentDispatcher 是什么?

    import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';const ReactCurrentDispatcher = {current: (null: null | Dispatcher), }

    我們最終找到了 packages/react-reconciler/src/ReactFiberHooks.js,在這里有 useState 具體實(shí)現(xiàn)。該文件也包含了所有 React Hooks 的核心處理邏輯。

    4.2 類型定義

    4.2.1 Hook

    在開(kāi)始之前,我們先看看 ReactFiberHooks.js 中幾個(gè)類型的定義。首先是 Hooks:

    export type Hook = {memoizedState: any, // 指向當(dāng)前渲染節(jié)點(diǎn) Fiber, 上一次完整更新之后的最終狀態(tài)值baseState: any, // 初始化 initialState, 已經(jīng)每次 dispatch 之后 newStatebaseUpdate: Update<any, any> | null, // 當(dāng)前需要更新的 Update ,每次更新完之后,會(huì)賦值上一個(gè) update,方便 react 在渲染錯(cuò)誤的邊緣,數(shù)據(jù)回溯queue: UpdateQueue<any, any> | null, // 緩存的更新隊(duì)列,存儲(chǔ)多次更新行為next: Hook | null, // link 到下一個(gè) hooks,通過(guò) next 串聯(lián)每一 hooks };

    可以看到,Hooks 的數(shù)據(jù)結(jié)構(gòu)和我們之前自己實(shí)現(xiàn)的基本一致,memoizedState 也是一個(gè)數(shù)組,準(zhǔn)確來(lái)說(shuō) React 的 Hooks 是一個(gè)單向鏈表,Hook.next 指向下一個(gè) Hook。

    4.2.2 Update & UpdateQueue

    那么 baseUpdate 和 queue 又是什么呢?先看一下 Update 和 UpdateQueue 的類型定義:

    type Update<S, A> = {expirationTime: ExpirationTime, // 當(dāng)前更新的過(guò)期時(shí)間suspenseConfig: null | SuspenseConfig,action: A,eagerReducer: ((S, A) => S) | null,eagerState: S | null,next: Update<S, A> | null, // link 下一個(gè) Updatepriority?: ReactPriorityLevel, // 優(yōu)先級(jí) };type UpdateQueue<S, A> = {last: Update<S, A> | null,dispatch: (A => mixed) | null,lastRenderedReducer: ((S, A) => S) | null,lastRenderedState: S | null, };

    Update 稱作一個(gè)更新,在調(diào)度一次 React 更新時(shí)會(huì)用到。UpdateQueue 是 Update 的隊(duì)列,同時(shí)還帶有更新時(shí)的 dispatch。具體的 React Fiber 和 React 更新調(diào)度的流程本篇不會(huì)涉及,后續(xù)會(huì)有單獨(dú)的文章補(bǔ)充講解。

    4.2.3 HooksDispatcherOnMount & HooksDispatcherOnUpdate

    還有兩個(gè) Dispatch 的類型定義需要關(guān)注一下,一個(gè)是首次加載時(shí)的 HooksDispatcherOnMount,另一個(gè)是更新時(shí)的 HooksDispatcherOnUpdate。

    const HooksDispatcherOnMount: Dispatcher = {readContext,useCallback: mountCallback,useContext: readContext,useEffect: mountEffect,useImperativeHandle: mountImperativeHandle,useLayoutEffect: mountLayoutEffect,useMemo: mountMemo,useReducer: mountReducer,useRef: mountRef,useState: mountState,useDebugValue: mountDebugValue,useResponder: createResponderListener, };const HooksDispatcherOnUpdate: Dispatcher = {readContext,useCallback: updateCallback,useContext: readContext,useEffect: updateEffect,useImperativeHandle: updateImperativeHandle,useLayoutEffect: updateLayoutEffect,useMemo: updateMemo,useReducer: updateReducer,useRef: updateRef,useState: updateState,useDebugValue: updateDebugValue,useResponder: createResponderListener, };

    4.3 首次渲染

    4.3.1 renderWithHooks

    React Fiber 會(huì)從 packages/react-reconciler/src/ReactFiberBeginWork.js 中的 beginWork() 開(kāi)始執(zhí)行(React Fiber 的具體流程后續(xù)單獨(dú)成文補(bǔ)充講解),對(duì)于 Function Component,其走以下邏輯加載或更新組件:

    case FunctionComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateFunctionComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}

    在 updateFunctionComponent 中,對(duì)于 Hooks 的處理是:

    nextChildren = renderWithHooks(current,workInProgress,Component,nextProps,context,renderExpirationTime, );

    因此,我們發(fā)現(xiàn) React Hooks 的渲染核心入口是 renderWithHooks。其他的渲染流程我們并不關(guān)心,本文我們著重來(lái)看看 renderWithHooks 及其之后的邏輯。

    我們回到 ReactFiberHooks.js 來(lái)看看 renderWithHooks 具體做了什么,去除容錯(cuò)代碼和 __DEV__ 的部分,renderWithHooks 代碼如下:

    export function renderWithHooks(current: Fiber | null,workInProgress: Fiber,Component: any,props: any,refOrContext: any,nextRenderExpirationTime: ExpirationTime, ): any {renderExpirationTime = nextRenderExpirationTime;currentlyRenderingFiber = workInProgress;nextCurrentHook = current !== null ? current.memoizedState : null;// The following should have already been reset// currentHook = null;// workInProgressHook = null;// remainingExpirationTime = NoWork;// componentUpdateQueue = null;// didScheduleRenderPhaseUpdate = false;// renderPhaseUpdates = null;// numberOfReRenders = 0;// sideEffectTag = 0;// TODO Warn if no hooks are used at all during mount, then some are used during update.// Currently we will identify the update render as a mount because nextCurrentHook === null.// This is tricky because it's valid for certain types of components (e.g. React.lazy)// Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.// Non-stateful hooks (e.g. context) don't get added to memoizedState,// so nextCurrentHook would be null during updates and mounts.ReactCurrentDispatcher.current =nextCurrentHook === null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;let children = Component(props, refOrContext);if (didScheduleRenderPhaseUpdate) {do {didScheduleRenderPhaseUpdate = false;numberOfReRenders += 1;// Start over from the beginning of the listnextCurrentHook = current !== null ? current.memoizedState : null;nextWorkInProgressHook = firstWorkInProgressHook;currentHook = null;workInProgressHook = null;componentUpdateQueue = null;ReactCurrentDispatcher.current = __DEV__? HooksDispatcherOnUpdateInDEV: HooksDispatcherOnUpdate;children = Component(props, refOrContext);} while (didScheduleRenderPhaseUpdate);renderPhaseUpdates = null;numberOfReRenders = 0;}// We can assume the previous dispatcher is always this one, since we set it// at the beginning of the render phase and there's no re-entrancy.ReactCurrentDispatcher.current = ContextOnlyDispatcher;const renderedWork: Fiber = (currentlyRenderingFiber: any);renderedWork.memoizedState = firstWorkInProgressHook;renderedWork.expirationTime = remainingExpirationTime;renderedWork.updateQueue = (componentUpdateQueue: any);renderedWork.effectTag |= sideEffectTag;// This check uses currentHook so that it works the same in DEV and prod bundles.// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.const didRenderTooFewHooks =currentHook !== null && currentHook.next !== null;renderExpirationTime = NoWork;currentlyRenderingFiber = null;currentHook = null;nextCurrentHook = null;firstWorkInProgressHook = null;workInProgressHook = null;nextWorkInProgressHook = null;remainingExpirationTime = NoWork;componentUpdateQueue = null;sideEffectTag = 0;// These were reset above// didScheduleRenderPhaseUpdate = false;// renderPhaseUpdates = null;// numberOfReRenders = 0;return children; }

    renderWithHooks 包括三個(gè)部分,首先是賦值 4.1 中提到的 ReactCurrentDispatcher.current,后續(xù)是做 didScheduleRenderPhaseUpdate 以及一些初始化的工作。核心是第一部分,我們來(lái)看看:

    nextCurrentHook = current !== null ? current.memoizedState : null;ReactCurrentDispatcher.current =nextCurrentHook === null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;

    如果當(dāng)前 Fiber 為空,就認(rèn)為是首次加載,ReactCurrentDispatcher.current.useState 將賦值成 HooksDispatcherOnMount.useState,否則賦值 HooksDispatcherOnUpdate.useState。根據(jù) 4.2 中的類型定義,即首次加載時(shí),useState = ReactCurrentDispatcher.current.useState = HooksDispatcherOnMount.useState = mountState;更新時(shí) useState = ReactCurrentDispatcher.current.useState = HooksDispatcherOnUpdate.useState = updateState。

    4.3.2 mountState

    首先看看 mountState 的實(shí)現(xiàn):

    // 第一次調(diào)用組件的 useState 時(shí)實(shí)際調(diào)用的方法 function mountState<S>(initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] {// 創(chuàng)建一個(gè)新的 Hook,并返回當(dāng)前 workInProgressHookconst hook = mountWorkInProgressHook();if (typeof initialState === 'function') {initialState = initialState();}hook.memoizedState = hook.baseState = initialState;// 新建一個(gè)隊(duì)列const queue = (hook.queue = {last: null, // 最后一次更新邏輯, 包括 {action,next} 即狀態(tài)值和下一次 Updatedispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: (initialState: any), // 最后一次渲染組件時(shí)的狀態(tài)});const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind(null,// 綁定當(dāng)前 fiber 和 queue.((currentlyRenderingFiber: any): Fiber),queue,): any));return [hook.memoizedState, dispatch]; }

    4.3.3 mountWorkInProgressHook

    mountWorkInProgressHook 是創(chuàng)建一個(gè)新的 Hook 并返回當(dāng)前 workInProgressHook,實(shí)現(xiàn)如下:

    // 創(chuàng)建一個(gè)新的 hook,并返回當(dāng)前 workInProgressHook function mountWorkInProgressHook(): Hook {const hook: Hook = {memoizedState: null,baseState: null,queue: null,baseUpdate: null,next: null,};// 只有在第一次打開(kāi)頁(yè)面的時(shí)候,workInProgressHook 為空if (workInProgressHook === null) {firstWorkInProgressHook = workInProgressHook = hook;} else {// 已經(jīng)存在 workInProgressHook 就將新創(chuàng)建的這個(gè) Hook 接在 workInProgressHook 的尾部。workInProgressHook = workInProgressHook.next = hook;}return workInProgressHook; }

    4.3.4 dispatchAction

    我們注意到 mountState 還做了一件很關(guān)鍵的事情,綁定當(dāng)前 fiber 和 queue 到 dispatchAction 上:

    const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind(null,// 綁定當(dāng)前 fiber 和 queue((currentlyRenderingFiber: any): Fiber),queue,): any));

    那我們看一下 dispatchAction 是如何實(shí)現(xiàn)的:

    function dispatchAction<S, A>(fiber: Fiber,queue: UpdateQueue<S, A>,action: A, ) {const alternate = fiber.alternate;if (fiber === currentlyRenderingFiber ||(alternate !== null && alternate === currentlyRenderingFiber)) {// 此分支為 re-render 時(shí)的 Fiber 調(diào)度處理didScheduleRenderPhaseUpdate = true;const update: Update<S, A> = {expirationTime: renderExpirationTime,suspenseConfig: null,action,eagerReducer: null,eagerState: null,next: null,};// 將本次更新周期里的更新記錄緩存進(jìn) renderPhaseUpdates 中if (renderPhaseUpdates === null) {renderPhaseUpdates = new Map();}const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);if (firstRenderPhaseUpdate === undefined) {renderPhaseUpdates.set(queue, update);} else {let lastRenderPhaseUpdate = firstRenderPhaseUpdate;while (lastRenderPhaseUpdate.next !== null) {lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;}lastRenderPhaseUpdate.next = update;}} else {const currentTime = requestCurrentTime();const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);// 存儲(chǔ)所有的更新行為,以便在 re-render 流程中計(jì)算最新的狀態(tài)值const update: Update<S, A> = {expirationTime,suspenseConfig,action,eagerReducer: null,eagerState: null,next: null,};// Append the update to the end of the list.const last = queue.last;if (last === null) {// This is the first update. Create a circular list.update.next = update;} else {// ... 更新循環(huán)鏈表const first = last.next;if (first !== null) {// Still circular.update.next = first;}last.next = update;}queue.last = update;// 省略特殊情況 Fiber NoWork 時(shí)的代碼// 創(chuàng)建一個(gè)更新任務(wù),執(zhí)行 fiber 的渲染scheduleWork(fiber, expirationTime);} }

    if 的第一個(gè)分支涉及 Fiber 的調(diào)度,我們此處僅是提及,本文不詳細(xì)講解 Fiber,只要知道 fiber === currentlyRenderingFiber 時(shí)是 re-render,即當(dāng)前更新周期中又產(chǎn)生了新的周期即可。如果是 re-render,didScheduleRenderPhaseUpdate 置為 true,而在 renderWithHooks 中 如果 didScheduleRenderPhaseUpdate 為 true,就會(huì)循環(huán)計(jì)數(shù) numberOfReRenders 來(lái)記錄 re-render 的次數(shù);另外 nextWorkInProgressHook 也會(huì)有值。所以后續(xù)的代碼中,有用 numberOfReRenders > 0 來(lái)判斷是否是 re-render 的,也有用 nextWorkInProgressHook 是否為空來(lái)判斷是否是 re-render 的。

    同時(shí),如果是 re-render,會(huì)把所有更新過(guò)程中產(chǎn)生的更新記錄在 renderPhaseUpdates 這個(gè) Map 上,以每個(gè) Hook 的 queue 為 key。

    至于最后 scheduleWork 的具體工作,我們后續(xù)單獨(dú)成文來(lái)分析。

    4.4 更新

    4.4.1 updateState

    我們看看更新過(guò)程中的 useState 時(shí)實(shí)際調(diào)用的方法 updateState:

    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {return typeof action === 'function' ? action(state) : action; }// 第一次之后每一次執(zhí)行 useState 時(shí)實(shí)際調(diào)用的方法 function updateState<S>(initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] {return updateReducer(basicStateReducer, (initialState: any)); }

    可以發(fā)現(xiàn),其實(shí) updateState 最終調(diào)用的其實(shí)是 updateReducer。對(duì)于 useState 觸發(fā)的 update action 來(lái)說(shuō),basicStateReducer 就是直接返回 action 的值(如果 action 是函數(shù)還會(huì)幫忙調(diào)用一下)。因此,useState 只是 useReduer 的一個(gè)特殊情況而已,其傳入的 reducer 為
    basicStateReducer,負(fù)責(zé)改變 state,而非 useReducer 那樣可以傳入自定義的 reducer。

    4.4.2 updateReducer

    那我們來(lái)看看 updateReducer 做了些什么:

    function updateReducer<S, I, A>(reducer: (S, A) => S,initialArg: I,init?: I => S, ): [S, Dispatch<A>] {// 獲取當(dāng)前正在工作中的 hookconst hook = updateWorkInProgressHook();const queue = hook.queue;queue.lastRenderedReducer = reducer;if (numberOfReRenders > 0) {// re-render:當(dāng)前更新周期中產(chǎn)生了新的更新const dispatch: Dispatch<A> = (queue.dispatch: any);if (renderPhaseUpdates !== null) {// 所有更新過(guò)程中產(chǎn)生的更新記錄在 renderPhaseUpdates 這個(gè) Map上,以每個(gè) Hook 的 queue 為 key。const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);if (firstRenderPhaseUpdate !== undefined) {renderPhaseUpdates.delete(queue);let newState = hook.memoizedState;let update = firstRenderPhaseUpdate;do {// 如果是 re-render,繼續(xù)執(zhí)行這些更新直到當(dāng)前渲染周期中沒(méi)有更新為止const action = update.action;newState = reducer(newState, action);update = update.next;} while (update !== null);if (!is(newState, hook.memoizedState)) {markWorkInProgressReceivedUpdate();}hook.memoizedState = newState;if (hook.baseUpdate === queue.last) {hook.baseState = newState;}queue.lastRenderedState = newState;return [newState, dispatch];}}return [hook.memoizedState, dispatch];}const last = queue.last;const baseUpdate = hook.baseUpdate;const baseState = hook.baseState;let first;if (baseUpdate !== null) {if (last !== null) {last.next = null;}first = baseUpdate.next;} else {first = last !== null ? last.next : null;}if (first !== null) {let newState = baseState;let newBaseState = null;let newBaseUpdate = null;let prevUpdate = baseUpdate;let update = first;let didSkip = false;do {const updateExpirationTime = update.expirationTime;if (updateExpirationTime < renderExpirationTime) {if (!didSkip) {didSkip = true;newBaseUpdate = prevUpdate;newBaseState = newState;}if (updateExpirationTime > remainingExpirationTime) {remainingExpirationTime = updateExpirationTime;}} else {markRenderEventTimeAndConfig(updateExpirationTime,update.suspenseConfig,);// 循環(huán)鏈表,執(zhí)行每一次更新if (update.eagerReducer === reducer) {newState = ((update.eagerState: any): S);} else {const action = update.action;newState = reducer(newState, action);}}prevUpdate = update;update = update.next;} while (update !== null && update !== first);if (!didSkip) {newBaseUpdate = prevUpdate;newBaseState = newState;}if (!is(newState, hook.memoizedState)) {markWorkInProgressReceivedUpdate();}hook.memoizedState = newState;hook.baseUpdate = newBaseUpdate;hook.baseState = newBaseState;queue.lastRenderedState = newState;}const dispatch: Dispatch<A> = (queue.dispatch: any);return [hook.memoizedState, dispatch]; }

    updateReducer 分為兩種情況:

  • 非 re-render,即當(dāng)前更新周期只有一個(gè) Update。
  • re-render,當(dāng)前更新周期又產(chǎn)生了新的更新。
  • 在 4.3.4 中我們提到 numberOfReRenders 記錄了 re-render 的次數(shù),如果大于 0 說(shuō)明當(dāng)前更新周期中又產(chǎn)生了新的更新,那么就繼續(xù)執(zhí)行這些更新,根據(jù) reducer 和 update.action 來(lái)創(chuàng)建新的 state,直到當(dāng)前渲染周期中沒(méi)有更新為止,最后賦值給 Hook.memoizedState 以及 Hook.baseState。

    注:其實(shí)單獨(dú)使用 useState 的話幾乎不會(huì)遇到 re-render 的場(chǎng)景,除非直接把 setState 寫(xiě)在函數(shù)的頂部,但是這樣會(huì)導(dǎo)致無(wú)限 re-render,numberOfReRenders 會(huì)突破限制,在 4.3.4 dispatchAction 中讓程序報(bào)錯(cuò)(4.3.4 隱去了 __DEV__ 與這部分容錯(cuò)代碼):invariant(numberOfReRenders < RE_RENDER_LIMIT,'Too many re-renders. React limits the number of renders to prevent ' +'an infinite loop.',);

    那么再來(lái)看一下非 re-render 的情況,除去 Fiber 相關(guān)的代碼和特殊邏輯,重點(diǎn)在于 do-while 循環(huán),這段代碼負(fù)責(zé)循環(huán)鏈表,執(zhí)行每一次更新:

    do {// 循環(huán)鏈表,執(zhí)行每一次更新if (update.eagerReducer === reducer) {newState = ((update.eagerState: any): S);} else {const action = update.action;newState = reducer(newState, action);}prevUpdate = update;update = update.next; } while (update !== null && update !== first);

    還有一點(diǎn)需要注意,在這種情況下需要對(duì)每一個(gè) update 判斷優(yōu)先級(jí),如果不是當(dāng)前整體更新優(yōu)先級(jí)內(nèi)的更新會(huì)被跳過(guò),第一個(gè)跳過(guò)的 update 會(huì)變成新的 Hook.baseUpdate。需要保證后續(xù)的更新要在 baseUpdate 更新之后的基礎(chǔ)上再次執(zhí)行,因此結(jié)果可能會(huì)不一樣。這里的具體邏輯后續(xù)會(huì)成文單獨(dú)解析。最后同樣需要賦值給 Hook.memoizedState 以及 Hook.baseState。

    4.4.3 updateWorkInProgressHook

    這里補(bǔ)充一下,注意到第一行代碼獲取 Hook 的方式就與 mountState 不同,updateWorkInProgressHook 是獲取當(dāng)前正在工作中的 Hook。實(shí)現(xiàn)如下:

    // 獲取當(dāng)前正在工作中的 Hook,即 workInProgressHook function updateWorkInProgressHook(): 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.currentHook = nextCurrentHook;const newHook: Hook = {memoizedState: currentHook.memoizedState,baseState: currentHook.baseState,queue: currentHook.queue,baseUpdate: currentHook.baseUpdate,next: null,};if (workInProgressHook === null) {workInProgressHook = firstWorkInProgressHook = newHook;} else {workInProgressHook = workInProgressHook.next = newHook;}nextCurrentHook = currentHook.next;}return workInProgressHook; }

    這里分為兩種情況,在 4.3.4 中我們提到如果 nextWorkInProgressHook 存在那么就是 re-render,如果是 re-render 說(shuō)明當(dāng)前更新周期中還要繼續(xù)處理 workInProgressHook。

    如果不是 re-render,就取下一個(gè) Hook 為當(dāng)前的 Hook,同時(shí)像 4.3.3 mountWorkInProgressHook 一樣,新建一個(gè) Hook 并返回 workInProgressHook。

    總之,updateWorkInProgressHook 獲取到了當(dāng)前工作中的 workInProgressHook。

    5 結(jié)語(yǔ)

    直觀一點(diǎn),我截了一個(gè) Hook 在運(yùn)行中的數(shù)據(jù)結(jié)構(gòu),如下圖所示:

    總結(jié)一下上文中解析的流程,如下圖所示:

    如果對(duì)于 useState 的源碼仍有所疑惑,可以自己寫(xiě)個(gè)小 Demo 在關(guān)鍵函數(shù)打斷點(diǎn)調(diào)試一下。

    總結(jié)

    以上是生活随笔為你收集整理的usestate中的回调函数_React Hooks 源码解析(3):useState的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。