注册

这几个关键的数据结构都不会,你也配学react源码???

不知道大家在学习react源码的时候有没有这样的感觉:fiber对象的结构太复杂了,不仅是属性繁多,而且有些属性还是个巨复杂的对象。我在学习hooks的时候,这种感觉尤为强烈。那么,这篇文章就以fiber.memoizedStatefiber.updateQueue这两个重要属性为例,梳理一下他们的结构和作用,希望对大家阅读源码有帮助。


先来看类组件


首先来看类组件。类组件的fiber.memoizedStatefiber.updateQueue比较简单,memoizedState就是我们定义的state对象,updateQueue的结构如下:


fiber.updateQueue {
baseState,
firstBaseUpdate,
lastBaseUpdate,
shared: {
pending,
},
effects
}

首先,前三个属性baseStatefirstBaseUpdatelastBaseUpdate是和可中断更新相关的。在concurrent mode下,当前的更新可能被更高优先级的更新打断,而baseState就记录了被跳过的update节点之前计算出的statefirstBaseStatelastBaseUpdate构成一条链表,记录因为优先级不足而被跳过的所有更新。shared.pending才是真正记录了所有update对象的链表,这是一条环形链表,shared.pending指向该链表的最后一个update对象,effects则是一个数组,存储了setState的回调函数。关于类组件的state计算原理,可以看我的这篇文章,这里面有更加详细的讲解,本文还是主要介绍数据结构。


看函数组件


了解hooks原理的同学应该都知道,react使用链表来存储函数组件中用到的所有hooks,而这个链表就保存在fiber.memoizedState。这个hooks链表的每一项都是hook对象,hook对象的结构如下


hook {
memoizedState,
baseState,
baseQueue,
queue,
next
}

hooks链表.jpg



没错,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;
}

useMemouseCallback基本一致,只不过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.updateQueuenull,也就是说和类组件不同,useState并没有用到updateQueue,这一点容易让人困惑。


useEffect


接下来看看我个人认为最复杂的hookuseEffect。说他复杂,是因为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进行了包装,此外还有useEffectuseLayoutEffect,下面看一下



  1. 首先,执行useState,因此在fiber.memoizedState上挂载了一个useStatehook对象

例子1.jpg



  1. 接下来执行到useCallback,在hooks链表中再添加一项

例子2.jpg



  1. 接下来又是useStateuseCallback,因此和前两步一样

例子3.jpg



  1. 之后来到比较复杂的useEffect了,useEffect不仅会添加hook对象到hooks链表中,还会修改updateQueue

例子4.jpg



  1. 后面还是一个useEffect,这一步就比较复杂了

例子5.jpg


可以看到,使用useEffect时,不仅构成了hooks链表,effect对象也构成了一条环形链表



  1. 之后执行了useLayoutEffect

例子6.jpg


最后来总结一下这个例子


例子总结.jpg


链接:https://juejin.cn/post/6993150359317250085

0 个评论

要回复文章请先登录注册