这几个关键的数据结构都不会,你也配学react源码???
不知道大家在学习react
源码的时候有没有这样的感觉:fiber
对象的结构太复杂了,不仅是属性繁多,而且有些属性还是个巨复杂的对象。我在学习hooks的时候,这种感觉尤为强烈。那么,这篇文章就以fiber.memoizedState
和fiber.updateQueue
这两个重要属性为例,梳理一下他们的结构和作用,希望对大家阅读源码有帮助。
先来看类组件
首先来看类组件。类组件的fiber.memoizedState
和fiber.updateQueue
比较简单,memoizedState
就是我们定义的state
对象,updateQueue
的结构如下:
fiber.updateQueue {
baseState,
firstBaseUpdate,
lastBaseUpdate,
shared: {
pending,
},
effects
}
首先,前三个属性baseState
、firstBaseUpdate
和lastBaseUpdate
是和可中断更新相关的。在concurrent mode
下,当前的更新可能被更高优先级的更新打断,而baseState
就记录了被跳过的update
节点之前计算出的state
,firstBaseState
和lastBaseUpdate
构成一条链表,记录因为优先级不足而被跳过的所有更新。shared.pending
才是真正记录了所有update
对象的链表,这是一条环形链表,shared.pending
指向该链表的最后一个update
对象,effects
则是一个数组,存储了setState
的回调函数。关于类组件的state
计算原理,可以看我的这篇文章,这里面有更加详细的讲解,本文还是主要介绍数据结构。
看函数组件
了解hooks
原理的同学应该都知道,react
使用链表来存储函数组件中用到的所有hooks
,而这个链表就保存在fiber.memoizedState
。这个hooks
链表的每一项都是hook
对象,hook
对象的结构如下
hook {
memoizedState,
baseState,
baseQueue,
queue,
next
}
没错,
fiber.memoizedState
指向了hook
链表,而每个hook
对象还有一个memoizedState
对象!!!
不论是哪种hook
,都会使用这一种数据结构,而有些hook
可能只会用到其中的几个属性。下面,就由简入繁,介绍不同的hook
都用到了哪些属性。
useCallback, useMemo, useRef
这三个hook
都只使用到了hook.memoizedState
这一个属性。
useCallback
接受一个需要缓存的函数和依赖数组,而hook.memoizedState
则保存了一个数组,数组内放了缓存函数和依赖数组
function mountCallback(callback, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
useMemo
和useCallback
基本一致,只不过useMemo
保存了函数的返回值
function mountMemo(nextCreate, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
至于useRef
就能简单了,保存一个对象即可
function mountRef(initialValue) {
var hook = mountWorkInProgressHook();
var ref = {
current: initialValue
};
{
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
这里使用Object.seal
封闭了ref
对象,Object.seal
就是在不可扩展(Object.preventExtensions
)的基础上不可配置(configurable
变为false
)。
useState
看完了三个简单的hook
,现在加大一点难度,看看useState
。
useState
就比较复杂了,用到了hook
对象身上的所有属性
hook {
memoizedState: 保存的state,
baseState:和类组件的updateQueue.baseState作用相同,
baseQueue: 和类组件的updateQueue.firstBaseUpdate和lastBaseUpdate作用相同,
queue: {
pending: 保存update对象的环状链表,
dispatch: dispatchAction,useState的第二个返回值,
lastRenderedReducer: 上次更新时用到的reducer,
lastRenderedState: 上次更新的state
}
}
这里需要注意一点,useState
中用到了hook.queue
这个属性,而hook.queue.pending
和类组件的updateQueue.shared.pending
比较类似,都是用来保存update
对象的。而使用useState
时,fiber.updateQueue
是null
,也就是说和类组件不同,useState
并没有用到updateQueue
,这一点容易让人困惑。
useEffect
接下来看看我个人认为最复杂的hook
:useEffect
。说他复杂,是因为useEffect
的数据结构很绕,下面一起来看一下
首先,关于hook
对象身上的几个属性,useEffect
只用到了一个,就是memoizedState
,而在memoizedState
上保存的又是一个effect
对象
hook {
memoized: {
tag: 用于区分是useEffect还是useLayoutEffect,
create: 就是useEffect的回调函数,
destory: useEffect回调的返回值,
deps: 依赖数组,
next: 下一个effect对象,构成环装链表
},
baseState: null,
baseQueue: null,
queue: null
}
此外,useEffect
还用到了fiber.updateQueue
,这个updateQueue
的结构还和类组件的不一样
// 类组件的updateQueue
{
baseState,
firstBaseUpdate,
lastBaseUpdate,
shared: {
pending
},
effects
}
// useEffect的updateQueue
{
lastEffect
}
useEffect
不仅将effect
对象放在了hook.memoized
上,还放在了fiber.updateQueue
上,并且都是环形链表
举个🌰
前面说了这么多,可能大家还是比较困惑,下面看个实际的例子
const App = () => {
const [num, setNum] = useState(1)
const handleClick1 = useCallback(() => {
setNum((pre) => pre + 1)
}, [])
const [count, setCount] = useState(1)
const handleClick2 = useCallback(() => {
setCount((pre) => pre + 1)
}, [])
useEffect(() => {
console.log('1st effect', num)
}, [num])
useEffect(() => {
console.log('2nd effect', num)
}, [num])
useLayoutEffect(() => {
console.log('use layout', count)
}, [count])
return (
<div>
<p>{num}</p>
<p>{count}</p>
<button onClick={handleClick1}>click me</button>
<button onClick={handleClick2}>click me</button>
</div>
)
}
上面的例子中,我们使用了两个useState
,并且每个事件处理函数都使用useCallback
进行了包装,此外还有useEffect
和useLayoutEffect
,下面看一下
- 首先,执行
useState
,因此在fiber.memoizedState
上挂载了一个useState
的hook
对象
- 接下来执行到
useCallback
,在hooks
链表中再添加一项
- 接下来又是
useState
和useCallback
,因此和前两步一样
- 之后来到比较复杂的
useEffect
了,useEffect
不仅会添加hook
对象到hooks
链表中,还会修改updateQueue
- 后面还是一个
useEffect
,这一步就比较复杂了
可以看到,使用useEffect
时,不仅构成了hooks
链表,effect
对象也构成了一条环形链表
- 之后执行了
useLayoutEffect
最后来总结一下这个例子
链接:https://juejin.cn/post/6993150359317250085