[FE] React Hook: useState & setState

背景

React 16.8 引入了 Hook 的概念,一直以来都没时间仔细研究它的实现方式,
本文从一个最简单的例子出发,记录了 [email protected] 的 useState 源码逻辑。

概览

示例代码:github: debug-react

function App() {
  debugger;
  const [state, setState] = useState(0);
  debugger;

  const increment = () => {
    debugger;
    setState(s => {
      debugger;
      return s + 1;
    });
    debugger;
    setState(s => {
      debugger;
      return s + 2;
    });
    debugger;
  };

  debugger;
  return ;
}

以上代码中,使用 useState 并设置初始值为 0,先完成了首次渲染。
然后,点击页面中的按钮,触发 increment 中的 setState 对页面进行更新。

精简版的业务逻辑如下,


image

标红的是首次渲染与更新不同的逻辑,留意以下几个函数,
(1)首次渲染:mountIndeterminateComponentmountStatecreateFiberFromElement
(2)更新:setStateupdateFunctionComponentupdateStateuseFiber

首次渲染

ReactDOM.render 触发,调用了组件的装载方法 mountIndeterminateComponent
接着就执行组件(函数 App)内部的逻辑了。

App 内部第一行就是 useState
它会创建了一个 hook,然后把它挂载到 fiber 上,
每个 hook 还包含了一个 queue

fiber hook quque 的关系如下,

fiber: {  // Fiber
  // 节点相关
  sibling, child, return, index, 

  // 状态相关
  memoizedState: {  // Hook
    memorizedState, baseState, baseQueue, next,
    queue: {  // UpdateQueue
      pending, dispatch, lastRenderedReducer, lastRenderedState,
    },
  },
  updateQueue, 
  
  // 渲染相关
  effectTag, nextEffect, alternate,
}

fiber 构成了一棵树,通过 sibling, child, return, index 相连。
多个 hook 构成一个循环链表,通过 next 相连。

建好这些数据结构之后,useState 就返回了,

return [hook.memoizedState, dispatch];

它返回了两个值,一个是 hook.memoizedState 表示 hook 的当前状态,
在首次渲染时,这个状态就是初始值 0
另一个是 dispatch 函数,也就是后文要用的 setState

useState 及外层的 App 组件函数执行完后,
mountIndeterminateComponent 会接着调用 reconcileChildren 创建新的 fiber

fiber 整棵树都更新完之后,
就执行 commitRoot 一次性的渲染到页面上。

更新

更新逻辑在渲染到页面之前,分为两步
(1)响应事件
(2)更新状态

(1)响应事件

点击页面上的按钮,React 会响应绑定到 button 上的 onClick 事件,
从而调用了 increment 回调函数。

在这个 increment 函数中,我们的示例调用了两次 setState
用于说明这个两个 setState 的处理逻辑其实是不同的

每个 setState 都会重新创建一个 update 对象,
update 构成了一个循环链表,用于记录所有待更新的操作,结构如下,

update: {
  expirationTime, suspenseConfig, action, 
  eagerReducer, eagerState, 
  next, priority,
}

虽然 update 对象都会创建,但却只有第一个 setStateupdate 会被执行。
剩下 setStateupdate 会记录在链表中。

(2)更新状态

update 链表中的后续操作,会在下次调用 useState 的时候执行。

仔细来看的话,React 响应完事件后,会进入 workLoopSync 循环,
然后调用了 updateFunctionComponent,更新组件状态。
此时会再次调用 App 函数,useState 也被再次调用。

然后 useState 执行了和首次渲染不同的逻辑,调用了 updateState
执行 update 循环链表中的所有的操作,
最后把更新的结果返回。

return [hook.memoizedState, dispatch];

此时 hook.memoizedState 是所有 setState 都执行后的结果了。

Reconcile & Commit

剩下的流程就跟首次渲染大同小异了,
先是调用 reconcileChildren 更新 fiber 树,然后 commitRoot 渲染到页面上。

总结

本文简单梳理了 [email protected] useStatesetState 的业务逻辑,
可以看到 hook 的主要神秘之处在于,update 暂存起来异步更新

以上分析还只是 React 源码的九牛之一毛,还需要不断的学习探索。

参考

github: debug-react
[email protected]

你可能感兴趣的:([FE] React Hook: useState & setState)