useState原理解析

一、初始化

构建dispatcher函数和初始值

二、更新时

  1. 调用dispatcher函数,按序插入update(其实就是一个action)

  2. 收集update,调度一次React的更新

  3. 在更新的过程中将ReactCurrentDispatcher.current指向负责更新的Dispatcher

  4. 执行到函数组件App()时,useState会被重新执行,在resolve dispatcher的阶段拿到了负责更新的dispatcher。

  5. useState会拿到Hook对象,Hook.query中存储了更新队列,依次进行更新后,即可拿到最新的state

  6. 函数组件App()执行后返回的nextChild中的count值已经是最新的了。FiberNode中的memorizedState也被设置为最新的state

  7. Fiber渲染出真实DOM。更新结束

三、 了解useState

useState的引入

// React.jsimport {useCallback,useContext,useEffect,useImperativeHandle,useDebugValue,useLayoutEffect,useMemo,useReducer,useRef,useState,} from './ReactHooks';

所有的Hooks在React.js中被引入,挂载在React对象中

useState的实现

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

重点都在这个dispatcher上,dispatcher通过resolveDispatcher()来获取,这个函数同样也很简单,只是将ReactCurrentDispatcher.current的值赋给了dispatcher。

// ReactHooks.jsfunction resolveDispatcher() {  const dispatcher = ReactCurrentDispatcher.current;  return dispatcher;}

ReactCurrentDispatcher.current.useStateuseState能够触发更新的关键原因,这个方法的实现并不在react包内。

四. 核心步骤分析

ReactFiberHooks.js包含着各种关于Hooks逻辑的处理

Hook对象的结构如下:

// ReactFiberHooks.jsexport type Hook = {  memoizedState: any,    baseState: any,      baseUpdate: Update<any, any> | null,    queue: UpdateQueue<any, any> | null,     next: Hook | null, };

在类组件中state是一整个对象,可以和memoizedState一一对应。但是在Hooks中,React并不知道我们调用了几次useState,所以React通过将一个Hook对象挂载在memorizedStated上来保存函数组件的state

重点关注memoizedStatenext

  • memoizedState是用来记录当前useState应该返回的结果的

  • query:缓存队列,存储多次更新行为

  • next:指向下一次useState对应的Hook对象。

renderWithHooks

renderWithHooks的运行过程如下:

// ReactFiberHooks.jsexport function renderWithHooks(  current: Fiber | null,  workInProgress: Fiber,  Component: any,  props: any,  refOrContext: any,  nextRenderExpirationTime: ExpirationTime,): any {  renderExpirationTime = nextRenderExpirationTime;  currentlyRenderingFiber = workInProgress;   // 如果current的值为空,说明还没有hook对象被挂载  // 而根据hook对象结构可知,current.memoizedState指向下一个current  nextCurrentHook = current !== null ? current.memoizedState : null;   // 用nextCurrentHook的值来区分mount和update,设置不同的dispatcher  ReactCurrentDispatcher.current =      nextCurrentHook === null      // 初始化时        ? HooksDispatcherOnMount          // 更新时        : HooksDispatcherOnUpdate;   // 此时已经有了新的dispatcher,在调用Component时就可以拿到新的对象  let children = Component(props, refOrContext);   // 重置  ReactCurrentDispatcher.current = ContextOnlyDispatcher;   const renderedWork: Fiber = (currentlyRenderingFiber: any);   // 更新memoizedState和updateQueue  renderedWork.memoizedState = firstWorkInProgressHook;  renderedWork.updateQueue = (componentUpdateQueue: any);    /** 省略与本文无关的部分代码,便于理解 **/}

初始化时

核心:创建一个新的hook,初始化state, 并绑定触发器

初始化阶段ReactCurrentDispatcher.current 会指向HooksDispatcherOnMount 对象

// ReactFiberHooks.js const HooksDispatcherOnMount: Dispatcher = {/** 省略其它Hooks **/  useState: mountState,}; // 所以调用useState(0)返回的就是HooksDispatcherOnMount.useState(0),也就是mountState(0)function mountState<S>(  initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {    // 访问Hook链表的下一个节点,获取到新的Hook对象  const hook = mountWorkInProgressHook();//如果入参是function则会调用,但是不提供参数  if (typeof initialState === 'function') {    initialState = initialState();  }// 进行state的初始化工作  hook.memoizedState = hook.baseState = initialState;// 进行queue的初始化工作  const queue = (hook.queue = {    last: null,    dispatch: null,    eagerReducer: basicStateReducer, // useState使用基础reducer    eagerState: (initialState: any),  });    // 返回触发器  const dispatch: Dispatch<BasicStateAction<S>,>     = (queue.dispatch = (dispatchAction.bind(        null,        //绑定当前fiber结点和queue        ((currentlyRenderingFiber: any): Fiber),        queue,  ));  // 返回初始state和触发器  return [hook.memoizedState, dispatch];} // 对于useState触发的update action来说(假设useState里面都传的变量),basicStateReducer就是直接返回action的值function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {  return typeof action === 'function' ? action(state) : action;}

更新函数 dispatchAction

function dispatchAction<S, A>(  fiber: Fiber,  queue: UpdateQueue<S, A>,  action: A,) {    /** 省略Fiber调度相关代码 **/   // 创建新的新的update, action就是我们setCount里面的值(count 1, count 2, count 3…)    const update: Update<S, A> = {      expirationTime,      action,      eagerReducer: null,      eagerState: null,      next: null,    };     // 重点:构建query    // queue.last是最近的一次更新,然后last.next开始是每一次的action    const last = queue.last;    if (last === null) {      // 只有一个update, 自己指自己-形成环      update.next = update;    } else {      const first = last.next;      if (first !== null) {         update.next = first;      }      last.next = update;    }    queue.last = update;     /** 省略特殊情况相关代码 **/     // 创建一个更新任务    scheduleWork(fiber, expirationTime); }

dispatchAction中维护了一份query的数据结构。

query是一个有环链表,规则:

  • query.last指向最近一次更新

  • last.next指向第一次更新

  • 后面就依次类推,最终倒数第二次更新指向last,形成一个环。

所以每次插入新update时,就需要将原来的first指向query.last.next。再将update指向query.next,最后将query.last指向update.

更新时

核心:获取该Hook对象中的 queue,内部存有本次更新的一系列数据,进行更新

更新阶段 ReactCurrentDispatcher.current 会指向HooksDispatcherOnUpdate对象

// ReactFiberHooks.js // 所以调用useState(0)返回的就是HooksDispatcherOnUpdate.useState(0),也就是updateReducer(basicStateReducer, 0) const HooksDispatcherOnUpdate: Dispatcher = {  /** 省略其它Hooks **/   useState: updateState,} function updateState(initialState) {  return updateReducer(basicStateReducer, initialState);} // 可以看到updateReducer的过程与传的initalState已经无关了,所以初始值只在第一次被使用 // 为了方便阅读,删去了一些无关代码// 查看完整代码:https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L606function updateReducer(reducer, initialArg, init) {// 获取初始化时的 hook  const hook = updateWorkInProgressHook();  const queue = hook.queue;   // 开始渲染更新  if (numberOfReRenders > 0) {    const dispatch = queue.dispatch;    if (renderPhaseUpdates !== null) {      // 获取Hook对象上的 queue,内部存有本次更新的一系列数据      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);      if (firstRenderPhaseUpdate !== undefined) {        renderPhaseUpdates.delete(queue);        let newState = hook.memoizedState;        let update = firstRenderPhaseUpdate;        // 获取更新后的state        do {          const action = update.action;          // 此时的reducer是basicStateReducer,直接返回action的值          newState = reducer(newState, action);          update = update.next;        } while (update !== null);        // 对 更新hook.memoized         hook.memoizedState = newState;        // 返回新的 state,及更新 hook 的 dispatch 方法        return [newState, dispatch];      }    }  } // 对于useState触发的update action来说(假设useState里面都传的变量),basicStateReducer就是直接返回action的值function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {  return typeof action === 'function' ? action(state) : action;}

总结

单个hooks的更新行为全都挂在Hooks.queue下,所以能够管理好queue的核心就在于

  • 初始化queue - mountState

  • 维护queue - dispatchAction

  • 更新queue - updateReducer

结合示例代码:

  • 当我们第一次调用[count, setCount] = useState(0)时,创建一个queue

  • 每一次调用setCount(x),就dispach一个内容为x的action(action的表现为:将count设为x),action存储在queue中,以前面讲述的有环链表规则来维护

  • 这些action最终在updateReducer中被调用,更新到memorizedState上,使我们能够获取到最新的state值。

文章就分享到这,欢迎关注“前端大神之路

来源:https://www.icode9.com/content-4-836451.html

(0)

相关推荐

  • Rematch的深入学习与实战应用(一),简易数字计数器

    摘要 近期在优化团队代码,发现Redux重复使用的代码过多. 经过调研发现了Rematch库:Redux是一个出色的状态管理工具,并且有着健全的中间件生态以及出色的开发工具:Rematch是没有boi ...

  • 当设计模式遇上 Hooks

    一  前言 「设计模式」是一个老生常谈的话题,但更多是集中在面向对象语言领域,如 C++,Java 等.前端领域对于设计模式的探讨热度并不是很高,很多人觉得对于 JavaScript 这种典型的面向过 ...

  • 简单的 useState 实现

    简单的 useState 实现 本文写于 2020 年 10 月 21 日 以下是一段非常简单的 React 代码: const App = () => { const [n, setN] = ...

  • iOS多线程:『GCD』详尽总结

    iOS多线程:『GCD』详尽总结

  • ReactHook快速上车

    React16.8开始内置了10个Hook,核心是2个: 状态管理:useState 副作用管理:useEffect 有状态的函数 useState 有状态组件写法: class Example ex ...

  • 不可忽视的调节阀高清动态图及原理解析,值得收藏

    公差帮APP2021-03-05 09:02:00 1.蝶阀 蝶阀 蝶阀的阀瓣是圆盘,围绕阀座内的一个轴旋转,旋角的大小,便是阀门的开闭度. 优点:轻巧.结构简单.比其他阀门要节省材料.开闭迅速,切断 ...

  • 神经张力手法原理解析与手法演示

    概念 神经张力手法又称之为神经松动术(Nerve Mobilization),是现代康复治疗技术中的技能之一,是治疗师利用神经走向(含中枢和周围神经系统)针对神经组织(含其结缔组织)施以机械性拉力而从 ...

  • 3D成像方法 汇总(原理解析)— 双目视觉、激光三角、结构光、ToF、光场、全息

    3D视觉工坊 201篇原创内容 公众号 3D成像方法汇总介绍: 这里要介绍的是真正的3D成像,得到物体三维的图形,是立体的图像.而不是利用人眼视觉差异的特点,错误感知到的假三维信息.  原理上分类:主 ...

  • 深度探索Linux操作系统:系统构建和原理解析

    前言 对于编译内核而言,一条make命令就足够了.构建内核最困难的地方不是编译,而是编译前的配置.配置内核时,通常我们都能找到一些参考. 比如,对于桌面系统,可以参考主流发行版的内核配置,比如,对于嵌 ...

  • django进阶:从WSGI的介绍到Django原理解析

    WEB前端开发社区 今天 WSGI(Web 服务器网关接口)是python中所定义的Web Server和Web APP之间或框架之间的接口标准规范.当使用 Python 进行 web 开发时,要深刻 ...

  • 【IDseq™ Ultra原理解析】当病原宏基因组(mNGS)遇上探针捕获

    常规病原宏基因组检测技术(mNGS)面临样本类型与潜在病原多样.检测分析背景嘈杂的困境,高效去宿主技术是精准检测的核心秘籍之一.对于下呼吸道样本,通过反向富集技术,可以很好地降低宿主占比,显著提高病原 ...

  • 正确太极拳和站桩的神奇原理解析

    铁骑兵2019-05-11阅读 462练拳不练功,到老一场空!练功不练腿,老来冒失鬼 练太极拳和站桩对腿的8大好处:1 加速腿部的血液循环,改善脚冰凉,腿部发沉2 腿部有六条经络,肝经,胆经,脾经,胃 ...

  • 常用电鱼机电路图 电鱼机种类电路图原理解析大全 KIA MOS管

    一.传统多谐振荡鱼机 : 二.振上振电鱼机 三.互推式电鱼机 四.变压器推动推式鱼机 五.自激式电逆变器电路 六.自激式鱼机 七.自激式鱼机 八.自激式鱼机 九.反激式自控鱼机 十.自激式鱼机 十一. ...

  • 物理容易混淆两原理解析

    一.对物理学的基本概念和规律的理解不够清晰,记忆不够准确.如热学中分子间作用力的特点.物体的内能.热力学第一定律.分子力做功与分子势能间的关系 [例析一]:分子间同时存在吸引力和排斥力,下列说法正确的 ...