useState源码分析

346 阅读2分钟

前言

最近在学习react源码,通过源码能够比较清楚的知道很多实现细节。本文主要是记录自己的理解。本文使用react源码版本:v16.8.6。

问题

  • useState只能在函数组件中使用,为什么?
  • useState让函数组件拥有自己的状态,如何实现的?

示例

接下来,我们从下面的简单例子说起。

function App() {
    const [ age, setAge ] = React.useState(10);
    const [ name, setName ] = React.useState('abc');
    
    return (
        <React.Fragment>
            <div>name: { name } - age: { age }</div>
            <span onClick={ () => setAge(age + 1) }>add</span>
        </React.Fragment>
    )
}

组件挂载

了解过React渲染流程都知道,React在挂载过程中对不同类型的组件都进行了区分处理。常见的有:类组件,函数组件,ContextProvider 和 ContextConsumer 等。 App组件属于 IndeterminateComponent 类型。

mountIndeterminateComponent

在处理组件过程中,比较核心的操作是给 ReactCurrentDispatcher$1.current 重新赋值,这个对象是实现 hooks 的核心,默认实现就是抛出异常。接下来执行组件具体逻辑,生成虚拟dom。

调用useState

在执行第一个 useState 时会生成一个对象,用于保存初始值,修改值的方法等属性。并且对象会被赋值在一个全局变量 firstWorkInProgressHook 上,第二次调用 useState 时处理逻辑一样,生成的对象被保存在 firstWorkInProgressHook.next 属性上。最终 firstWorkInProgressHook 被赋值给组件对应的 FiberNode 对象 memoizedState 属性。

生成虚拟dom

至此,渲染所需的数据已经准备完成。后面会返回具体的虚拟dom,并映射成真实dom。具体的流程比较复杂,就不详细阐述了。有兴趣的小伙伴可以观看B站大神详细的视频讲解

组件更新

更新流程核心思路就是收集数据的变更,保存在FiberNode对象的memoizedState.upate属性中,然后进行重新渲染。

更新流程

更新流程中,重新渲染App组件会走到 updateFunctionComponent 逻辑。其中有两个比较核心的操作:

  • 赋值 ReactCurrentDispatcher.current (调用useState时使用)
  • 赋值 nextCurrentHook 为 FiberNode.memoizedState (找到第更新队列的第一个值)

然后,重新执行 useState 时,会根据 memoizedState.queue 队列中保存的更新信息,对 baseState 属性进行赋值。至此,更新需要的数据都获取到,最后重新渲染。

最后

写的过程中,发现很多东西都没介绍,需要对react源码有一定了解才能看懂。