前言
最近在学习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源码有一定了解才能看懂。