欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

react源碼層探究setState作用

 更新時(shí)間:2022年10月24日 14:24:39   作者:flyzz177  
寫react的時(shí)候,踩了幾次坑發(fā)現(xiàn)setstate之后state不會(huì)立刻更新,于是判定setstate就是異步的方法,但是直到有一天,我想立刻拿到更新的state去傳參另一個(gè)方法的時(shí)候,才問(wèn)自己,為什么setstate是異步的?準(zhǔn)確地說(shuō),在React內(nèi)部機(jī)制能檢測(cè)到的地方,setState就是異步的

前言

在深究 React 的 setState 原理的時(shí)候,我們先要考慮一個(gè)問(wèn)題:setState 是異步的嗎?

首先以 class component 為例,請(qǐng)看下述代碼(demo-0)

class App extends React.Component {
  state = {
    count: 0
  }
  handleCountClick = () => {
    this.setState({
      count: this.state.count + 1
    });
    console.log(this.state.count);
  }
  render() {
    return (
      <div className='app-box'>
        <div onClick={this.handleCountClick}>the count is {this.state.count}</div>
      </div>
    )
  }
}
ReactDOM.render(
    <App />,
  document.getElementById('container')
);

count初始值為 0,當(dāng)我們觸發(fā)handleCountClick事件的時(shí)候,執(zhí)行了count + 1操作,并打印了count,此時(shí)打印出的count是多少呢?答案不是 1 而是 0

類似的 function component 與 class component 原理一致?,F(xiàn)在我們以 function component 為例,請(qǐng)看下述代碼 (demo-1)

const App = function () {
  const [count, setCount] = React.useState(0);
  const handleCountClick = () => {
      setCount((count) => {
          return count + 1;
      });
      console.log(count);
  }
  return <div className='app-box'>
    <div onClick={handleCountClick}>the count is {count}</div>
  </div>
}
ReactDOM.render(
    <App />,
  document.getElementById('container')
);

同樣的,這里打印出的 count 也為 0

相信大家都知道這個(gè)看起來(lái)是異步的現(xiàn)象,但他真的是異步的嗎?

為什么setState看起來(lái)是異步的

首先得思考一個(gè)問(wèn)題:如何判斷這個(gè)函數(shù)是否為異步?

最直接的,我們寫一個(gè) setTimeout,打個(gè) debugger 試試看

我們都知道 setTimeout 里的回調(diào)函數(shù)是異步的,也正如上圖所示,chrome 會(huì)給 setTimeout 打上一個(gè) async 的標(biāo)簽。

接下來(lái)我們 debugger setState 看看

React.useState 返回的第二個(gè)參數(shù)實(shí)際就是這個(gè) dispatchSetState函數(shù)(下文細(xì)說(shuō))。但正如上圖所示,這個(gè)函數(shù)并沒(méi)有 async 標(biāo)簽,所以 setState 并不是異步的。

那么拋開這些概念來(lái)看,上文中 demo-1 的類似異步的現(xiàn)象是怎么發(fā)生的呢?

簡(jiǎn)單的來(lái)說(shuō),其步驟如下所示?;诖?,我們接下來(lái)更深入的看看 React 在這個(gè)過(guò)程中做了什么

從first paint開始

first paint 就是『首次渲染』,為突出顯示,就用英文代替。

這里先簡(jiǎn)單看一下App往下的 fiber tree 結(jié)構(gòu)。每個(gè) fiber node 還有一個(gè)return指向其 parent fiber node,這里就不細(xì)說(shuō)了

我們都知道 React 渲染的時(shí)候,得遍歷一遍 fiber tree,當(dāng)走到 App 這個(gè) fiber node 的時(shí)候發(fā)生了什么呢?

接下來(lái)我們看看詳細(xì)的代碼(這里的 workInProgress 就是整在處理的 fiber node,不關(guān)心的代碼已刪除)

首先要注意的是,雖然 App 是一個(gè) FunctionComponent,但是在 first paint 的時(shí)候,React 判斷其為 IndeterminateComponent。

 switch (workInProgress.tag) { // workInProgress.tag === 2
  case IndeterminateComponent:
    {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes
      );
    }
  // ...
  case FunctionComponent:
    { /** ... */}
}

接下來(lái)走進(jìn)這個(gè) mountIndeterminateComponent,里頭有個(gè)關(guān)鍵的函數(shù) renderWithHooks;而在 renderWithHooks 中,我們會(huì)根據(jù)組件處于不同的狀態(tài),給 ReactCurrentDispatcher.current 掛載不同的 dispatcher 。而在first paint 時(shí),掛載的是HooksDispatcherOnMountInDEV

 function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
     value = renderWithHooks(
         null,
         workInProgress,
         Component,
         props,
         context,
         renderLanes
     );
 }
 function renderWithHooks() {
     // ...
     if (current !== null && current.memoizedState !== null) {
         // 此時(shí) React 認(rèn)為組件在更新
         ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
     } else if (hookTypesDev !== null) {
         // handle edge case,這里我們不關(guān)心
     } else {
         // 此時(shí) React 認(rèn)為組件為 first paint 階段
         ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
     }
     // ...
     var children = Component(props, secondArg); // 調(diào)用我們的 Component
 }

這個(gè) HooksDispatcherOnMountInDEV 里就是組件 first paint 的時(shí)候所用到的各種 hooks,相關(guān)參考視頻講解:進(jìn)入學(xué)習(xí)

HooksDispatcherOnMountInDEV = {
    // ...
    useState: function (initialState) {
        currentHookNameInDev = 'useState';
        mountHookTypesDev();
        var prevDispatcher = ReactCurrentDispatcher$1.current;
        ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
        try {
          return mountState(initialState);
        } finally {
          ReactCurrentDispatcher.current = prevDispatcher;
        }
    },
    // ...
}

接下里走進(jìn)我們的 App(),我們會(huì)調(diào)用 React.useState,點(diǎn)進(jìn)去看看,代碼如下。這里的 dispatcher 就是上文掛載到 ReactCurrentDispatcher.currentHooksDispatcherOnMountInDEV

function useState(initialState) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useState(initialState);
}
// ...
HooksDispatcherOnMountInDEV = {
    // ...
    useState: function (initialState) {
        currentHookNameInDev = 'useState';
        mountHookTypesDev();
        var prevDispatcher = ReactCurrentDispatcher$1.current;
        ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
        try {
          return mountState(initialState);
        } finally {
          ReactCurrentDispatcher$1.current = prevDispatcher;
        }
    },
    // ...
}

這里會(huì)調(diào)用 mountState 函數(shù)

function mountState(initialState) {
    var hook = mountWorkInProgressHook();
    if (typeof initialState === 'function') {
      // $FlowFixMe: Flow doesn't like mixed types
      initialState = initialState();
    }
    hook.memoizedState = hook.baseState = initialState;
    var queue = {
      pending: null,
      interleaved: null,
      lanes: NoLanes,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: initialState
    };
    hook.queue = queue;
    var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
    return [hook.memoizedState, dispatch];
}

這個(gè)函數(shù)做了這么幾件事情:

執(zhí)行 mountWorkInProgressHook 函數(shù):

function mountWorkInProgressHook() {
   var hook = {
     memoizedState: null,
     baseState: null,
     baseQueue: null,
     queue: null,
     next: null
   };
   if (workInProgressHook === null) {
     // This is the first hook in the list
     currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
   } else {
     // Append to the end of the list
     workInProgressHook = workInProgressHook.next = hook;
   }
   return workInProgressHook;
 }
  • 創(chuàng)建一個(gè) hook
  • 若無(wú) hook 鏈,則創(chuàng)建一個(gè) hook 鏈;若有,則將新建的 hook 加至末尾
  • 將新建的這個(gè) hook 掛載到 workInProgressHook 以及當(dāng)前 fiber node 的 memoizedState
  • 返回 workInProgressHook,也就是這個(gè)新建的 hook

判斷傳入的 initialState 是否為一個(gè)函數(shù),若是,則調(diào)用它并重新賦值給 initialState (在我們的demo-1里是『0』)

initialState 掛到 hook.memoizedState 以及 hook.baseState

hook 上添加一個(gè) queue。這個(gè) queue 有多個(gè)屬性,其中queue.dispatch 掛載的是一個(gè) dispatchSetState。這里要注意一下這一行代碼

var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);

Function.prototype.bind 的第一個(gè)參數(shù)都知道是綁 this 的,后面兩個(gè)就是綁定了 dispatchSetState 所需要的第一個(gè)參數(shù)(當(dāng)前fiber)和第二個(gè)參數(shù)(當(dāng)前queue)。

這也是為什么雖然 dispatchSetState 本身需要三個(gè)參數(shù),但我們使用的時(shí)候都是 setState(params),只用傳一個(gè)參數(shù)的原因。

返回一個(gè)數(shù)組,也就是我們常見的 React.useState 返回的形式。此時(shí)這個(gè) state 是 0 至此為止,React.useState 在 first paint 里做的事兒就完成了,接下來(lái)就是正常渲染,展示頁(yè)面

觸發(fā)組件更新

要觸發(fā)組件更新,自然就是點(diǎn)擊這個(gè)綁定了事件監(jiān)聽的 div,觸發(fā) setCount?;貞浺幌?,這個(gè) setCount 就是上文講述的,暴露出來(lái)的 dispatchSetState。并且正如上文所述,我們傳進(jìn)去的參數(shù)實(shí)際上是 dispatchSetState 的第三個(gè)參數(shù) action。(這個(gè)函數(shù)自然也涉及一些 React 執(zhí)行優(yōu)先級(jí)的判斷,不在本文的討論范圍內(nèi)就省略了)

function dispatchSetState(fiber, queue, action) {
    var update = {
      lane: lane,
      action: action,
      hasEagerState: false,
      eagerState: null,
      next: null
    };
    enqueueUpdate(fiber, queue, update);
}

dispatchSetState 做了這么幾件事

創(chuàng)建一個(gè) update,把我們傳入的 action 放進(jìn)去

進(jìn)入 enqueueUpdate 函數(shù):

  • queue上無(wú) update 鏈,則在 queue 上以 剛創(chuàng)建的 update 為頭節(jié)點(diǎn)構(gòu)建 update
  • queue上有 update 鏈,則在該鏈的末尾添加這個(gè) 剛創(chuàng)建的 update
function enqueueUpdate(fiber, queue, update, lane) {
  var pending = queue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
  var lastRenderedReducer = queue.lastRenderedReducer;
  var currentState = queue.lastRenderedState;
  var eagerState = lastRenderedReducer(currentState, action);
  update.hasEagerState = true;
  update.eagerState = eagerState;
}
  • 根據(jù) queue 上的各個(gè)參數(shù)(reducer、上次計(jì)算出的 state)計(jì)算出 eagerState,并掛載到當(dāng)前 update

到此,我們實(shí)際上更新完 state了,這個(gè)新的 state 掛載到哪兒了呢?在 fiber.memoizedState.queue.pending 上。注意:

  • fiber 即為當(dāng)前的遍歷到的 fiber node;
  • pending 是一個(gè)環(huán)狀鏈表

此時(shí)我們打印進(jìn)行打印,但這里打印的還是 first paint 里返回出來(lái)的 state,也就是 0

更新渲染fiber tree

現(xiàn)在我們更新完 state,要開始跟新 fiber tree 了,進(jìn)行最后的渲染。邏輯在 performSyncWorkOnRoot 函數(shù)里,同樣的,不關(guān)心的邏輯我們省略

function performSyncWorkOnRoot(root) {
    var exitStatus = renderRootSync(root, lanes);
}

同樣的我們先看一眼 fiber tree 更新過(guò)程中 與 useState 相關(guān)的整個(gè)流程圖

首先我們走進(jìn) renderRootSync,這個(gè)函數(shù)作用是遍歷一遍 fiber tree,當(dāng)遍歷的 App時(shí),此時(shí)的類型為 FunctionComponent。還是我們前文所說(shuō)的熟悉的步驟,走進(jìn) renderWithHooks。注意此時(shí) React 認(rèn)為該組件在更新了,所以給 dispatcher 掛載的就是 HooksDispatcherOnUpdateInDEV

function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
    var children = Component(props, secondArg);
}

我們?cè)俅巫哌M(jìn) App,這里又要再次調(diào)用 React.useState

const App = function () {
      const [count, setCount] = React.useState(0);
      const handleCountClick = () => {
        setCount(count + 1);
      }
      return <div className='app-box'>
        <div onClick={handleCountClick}>the count is {count}</div>
      </div>
}

與之前不同的是,這次所使用的 dispatchHooksDispatcherOnUpdateInDEV。那么這個(gè) dispatch 下的 useState 具體做了什么呢?

useState: function (initialState) {
        currentHookNameInDev = 'useState';
        updateHookTypesDev();
        var prevDispatcher = ReactCurrentDispatcher$1.current;
        ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
        try {
          return updateState(initialState);
        } finally {
          ReactCurrentDispatcher$1.current = prevDispatcher;
        }
}

可以看到大致都差不多,唯一不同的是,這里調(diào)用的是 updateState,而之前是 mountState

function updateState(initialState) {
    return updateReducer(basicStateReducer);
}
function updateReducer(reducer, initialArg, init) {
  var first = baseQueue.next;
  var newState = current.baseState;
  do {
    // 遍歷更新 newState
    update = update.next;
  } while (update !== null && update !== first);
  hook.memoizedState = newState;
  queue.lastRenderedState = newState;
  return [hook.memoizedState, dispatch];
}

這里又調(diào)用了 updateReducer,其中代碼很多不一一展示,關(guān)鍵步驟就是:

  • 遍歷我們之前掛載到 fiber.memoizedState.queue.pending 上的環(huán)狀鏈表,并得到最后的 newState
  • 更新 hook、queue 上的相關(guān)屬性,也就是將最新的這個(gè) state 記錄下來(lái),這樣下次更新的時(shí)候可以這次為基礎(chǔ)再去更新
  • 返回一個(gè)數(shù)組,形式為 [state, setState],此時(shí)這個(gè) state 即為計(jì)算后的 newState,其值為 1

接下來(lái)就走進(jìn) commitRootImpl 進(jìn)行最后的渲染了,這不是本文的重點(diǎn)就不展開了,里頭涉及 useEffect 等鉤子函數(shù)的調(diào)用邏輯。

最后看一眼整個(gè)詳細(xì)的流程圖

寫在最后

上文只是描述了一個(gè)最簡(jiǎn)單的 React.useState 使用場(chǎng)景,各位可以根據(jù)本文配合源碼,進(jìn)行以下兩個(gè)嘗試:

Q1. 多個(gè) state 的時(shí)候有什么變化?例如以下場(chǎng)景時(shí):

const App = () => {
    const [count, setCount] = React.useState(0);
    const [str, setStr] = React.useState('');
    // ...
}

A1. 將會(huì)構(gòu)建一個(gè)上文所提到的 hook

Q2. 對(duì)同個(gè) state 多次調(diào)用 setState 時(shí)有什么變化?例如以下場(chǎng)景:

const App = () => {
      const [count, setCount] = React.useState(0);
      const handleCountClick = () => {
        setCount(count + 1);
        setCount(count + 2);
      }
      return <div className='app-box'>
        <div onClick={handleCountClick}>the count is {count}</div>
      </div>
}

A2. 將會(huì)構(gòu)建一個(gè)上文所提到的 update

到此這篇關(guān)于react源碼層探究setState作用的文章就介紹到這了,更多相關(guān)react setState內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React 中的 ForwardRef的使用示例詳解

    React 中的 ForwardRef的使用示例詳解

    forwardRef 相當(dāng)于是為 ref 傳遞的一種方式,普通的函數(shù)式組件就是 Render,而 fowardRef 多加了 Ref 參數(shù),這篇文章主要介紹了React 中的 ForwardRef的使用示例詳解,需要的朋友可以參考下
    2024-06-06
  • React Native中TabBarIOS的簡(jiǎn)單使用方法示例

    React Native中TabBarIOS的簡(jiǎn)單使用方法示例

    最近在學(xué)習(xí)過(guò)程中遇到了很多問(wèn)題,TabBarIOS的使用就是一個(gè),所以下面這篇文章主要給大家介紹了關(guān)于React Native中TabBarIOS簡(jiǎn)單使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-10-10
  • React框架快速實(shí)現(xiàn)簡(jiǎn)易的Markdown編輯器

    React框架快速實(shí)現(xiàn)簡(jiǎn)易的Markdown編輯器

    這篇文章主要為大家介紹了使用React框架實(shí)現(xiàn)簡(jiǎn)易的Markdown編輯器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • 淺談react 同構(gòu)之樣式直出

    淺談react 同構(gòu)之樣式直出

    這篇文章主要介紹了淺談react 同構(gòu)之樣式直出,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-11-11
  • React?Hooks之useDeferredValue鉤子用法示例詳解

    React?Hooks之useDeferredValue鉤子用法示例詳解

    useDeferredValue鉤子的主要目的是在React的并發(fā)模式中提供更流暢的用戶體驗(yàn),特別是在有高優(yōu)先級(jí)和低優(yōu)先級(jí)更新的情況下,本文主要講解一些常見的使用場(chǎng)景及其示例
    2023-09-09
  • 如何在 React 中調(diào)用多個(gè) onClick 函數(shù)

    如何在 React 中調(diào)用多個(gè) onClick 函數(shù)

    這篇文章主要介紹了如何在React中調(diào)用多個(gè)onClick函數(shù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • ahooks解決用戶多次提交方法示例

    ahooks解決用戶多次提交方法示例

    這篇文章主要為大家介紹了ahooks解決用戶多次提交的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • React Hook useState useEffect componentDidMount componentDidUpdate componentWillUnmount問(wèn)題

    React Hook useState useEffect componentD

    這篇文章主要介紹了React Hook useState useEffect componentDidMount componentDidUpdate componentWillUnmount問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React中的useEffect(副作用)介紹

    React中的useEffect(副作用)介紹

    這篇文章主要介紹了React中的useEffect(副作用),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • React-Native 環(huán)境搭建和基本介紹

    React-Native 環(huán)境搭建和基本介紹

    這篇文章主要介紹了React-Native 環(huán)境搭建和基本介紹的相關(guān)資料,包括react native優(yōu)缺點(diǎn),通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-04-04

最新評(píng)論