【JS】React hooks 的基础概念:hooks链表

React hooks 的基础概念:hooks链表

nero发布于 今天 12:52

当函数组件进入render阶段时,会被renderWithHooks函数处理。函数组件作为一个函数,它的渲染其实就是函数调用,而函数组件又会调用React提供的hooks函数。初始挂载和更新时,所用的hooks函数是不同的,比如初次挂载时调用的useEffect,和后续更新时调用的useEffect,虽然都是同一个hook,但是因为在两个不同的渲染过程中调用它们,所以本质上他们两个是不一样的。这种不一样来源于函数组件要维护一个hooks的链表,初次挂载时要创建链表,后续更新的时候要更新链表。

分属于两个过程的hook函数会在各自的过程中被赋值到ReactCurrentDispatcher的current属性上。所以在调用函数组件之前,当务之急是根据当前所处的阶段来决定ReactCurrentDispatcher的current,这样才可以在正确的阶段调用到正确的hook函数。

export function renderWithHooks<Props, SecondAwrg>(

current: Fiber | null,

workInProgress: Fiber,

Component: (p: Props, arg: SecondArg) => any,

props: Props,

secondArg: SecondArg,

nextRenderLanes: Lanes,

): any {

// 区分是挂载还是更新过程,获取不同的hooks函数集合

ReactCurrentDispatcher.current =

current === null || current.memoizedState === null

? HooksDispatcherOnMount

: HooksDispatcherOnUpdate;

// 调用函数组件,

let children = Component(props, secondArg);

...

return children;

}

HooksDispatcherOnMountHooksDispatcherOnUpdate,它们内部的hooks函数是不同的实现,区别之一在于不同阶段对于hooks链表的处理是不同的。

const HooksDispatcherOnMount: Dispatcher = {

useCallback: mountCallback,

useContext: readContext,

useEffect: mountEffect,

...

};

const HooksDispatcherOnUpdate: Dispatcher = {

useCallback: updateCallback,

useContext: readContext,

useEffect: updateEffect,

...

};

认识hooks链表

无论是初次挂载还是更新,每调用一次hooks函数,都会产生一个hook对象与之对应。以下是hook对象的结构。

{

baseQueue: null,

baseState: 'hook1',

memoizedState: null,

queue: null,

next: {

baseQueue: null,

baseState: null,

memoizedState: 'hook2',

next: null

queue: null

}

}

产生的hook对象依次排列,形成链表存储到函数组件fiber.memoizedState上。在这个过程中,有一个十分重要的指针:workInProgressHook,它通过记录当前生成(更新)的hook对象,可以间接反映在组件中当前调用到哪个hook函数了。每调用一次hook函数,就将这个指针的指向移到该hook函数产生的hook对象上。例如:

const HooksExp = () => {

const [ stateHookA, setHookA ] = useState('A')

useEffect(() => { console.log('B') })

const [ stateHookC, setHookC ] = useState('C')

return <div>Hook Example</div>

}

上面的例子中,HooksExp组件内一共调用了三个hooks函数,分别是useState、useEffect、useState。那么构建hook链表的过程,可以概括为下面这样,重点关注workInProgressHook的指向变化。

调用useState('A'):

fiber.memoizedState: hookA

^

workInProgressHook

调用useEffect:

fiber.memoizedState: hookA -> hookB

^

workInProgressHook

调用useState('C'):

fiber.memoizedState: hookA -> hookB -> hookC

^

workInProgressHook

hook函数每次执行,都会创建它对应的hook对象,去进行下一步的操作,比如useReducer会在hook对象上挂载更新队列,useEffect会在hook对象上挂载effect链表。而创建hook对象的过程实际上也是hooks链表构建以及workInProgressHook指针指向更新的过程。

组件挂载

初次挂载时,组件上没有任何hooks的信息,所以,这个过程主要是在fiber上创建hooks链表。挂载调用的是mountWorkInProgressHook,它会创建hook并将他们连接成链表,同时更新workInProgressHook,最终返回新创建的hook,也就是hooks链表。

function mountWorkInProgressHook(): Hook {

// 创建hook对象

const hook: Hook = {

memoizedState: null,

baseState: null,

baseQueue: null,

queue: null,

next: null,

};

if (workInProgressHook === null) {

// workInProgressHook为null说明此时还没有hooks链表,

// 将新hook对象作为第一个元素挂载到fiber.memoizedState,

// 并将workInProgressHook指向它。

currentlyRenderingFiber.memoizedState = workInProgressHook = hook;

} else {

// workInProgressHook不为null说明已经有hooks链表,此时将

// 新的hook对象连接到链表后边,并将workInProgressHook指向它。

workInProgressHook = workInProgressHook.next = hook;

}

// 返回的workInProgressHook即为新创建的hook

return workInProgressHook;

}

我们在组件中调用hook函数,就可以获取到hook对象,例如useState:

const HooksDispatcherOnMount: Dispatcher = {

...

useState: mountState,

...

};

function mountState<S>(

initialState: (() => S) | S,

): [S, Dispatch<BasicStateAction<S>>] {

// 获取hook对象

const hook = mountWorkInProgressHook();

// 对hook对象的处理

...

return [hook.memoizedState, dispatch];

}

组件更新

在更新过程中,由于存在current树,所以workInProgress节点也就有对应的current节点。那么自然也会有两条hooks链表,分别存在于current和workInProgress节点的memorizedState属性上。鉴于此,更新过程的hooks链表构建需要另一个指针的参与:currentHook。它作为组件的workInProgressHook在上一次更新时对应的hook对象,新的hook对象可以基于它创建。另外,也可以获取到上次hook对象的一些数据,例如useEffect的前后依赖项比较,前一次的依赖项就可以通过它获得。

                currentTree

current.memoizedState = hookA -> hookB -> hookC

^

currentHook

|

workInProgress Tree |

|

workInProgress.memoizedState = hookA -> hookB

^

workInProgressHook

所以更新过程的hooks链表构建过程除了更新workInProgressHook指针的指向,还要更新currentHook的指向,以及尽可能复用currentHook来创建新的hook对象。
这个过程调用的是updateWorkInProgressHook函数:

function updateWorkInProgressHook(): Hook {

// 确定nextCurrentHook的指向

let nextCurrentHook: null | Hook;

if (currentHook === null) {

// currentHook在函数组件调用完成时会被设置为null,

// 这说明组件是刚刚开始重新渲染,刚刚开始调用第一个hook函数。

// hooks链表为空

const current = currentlyRenderingFiber.alternate;

if (current !== null) {

// current节点存在,将nextCurrentHook指向current.memoizedState

nextCurrentHook = current.memoizedState;

} else {

nextCurrentHook = null;

}

} else {

// 这说明已经不是第一次调用hook函数了,

// hooks链表已经有数据,nextCurrentHook指向当前的下一个hook

nextCurrentHook = currentHook.next;

}

// 确定nextWorkInProgressHook的指向

let nextWorkInProgressHook: null | Hook;

if (workInProgressHook === null) {

// workInProgress.memoizedState在函数组件每次渲染时都会被设置成null,

// workInProgressHook在函数组件调用完成时会被设置为null,

// 所以当前的判断分支说明现在正调用第一个hook函数,hooks链表为空

// 将nextWorkInProgressHook指向workInProgress.memoizedState,为null

nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;

} else {

// 走到这个分支说明hooks链表已经有元素了,将nextWorkInProgressHook指向

// hooks链表的下一个元素

nextWorkInProgressHook = workInProgressHook.next;

}

if (nextWorkInProgressHook !== null) {

// 依据上面的推导,nextWorkInProgressHook不为空说明hooks链表不为空

// 更新workInProgressHook、nextWorkInProgressHook、currentHook

workInProgressHook = nextWorkInProgressHook;

nextWorkInProgressHook = workInProgressHook.next;

currentHook = nextCurrentHook;

} else {

// 走到这个分支说明hooks链表为空

// 刚刚调用第一个hook函数,基于currentHook新建一个hook对象,

invariant(

nextCurrentHook !== null,

'Rendered more hooks than during the previous render.',

);

currentHook = nextCurrentHook;

const newHook: Hook = {

memoizedState: currentHook.memoizedState,

baseState: currentHook.baseState,

baseQueue: currentHook.baseQueue,

queue: currentHook.queue,

next: null,

};

// 依据情况构建hooks链表,更新workInProgressHook指针

if (workInProgressHook === null) {

currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;

} else {

workInProgressHook = workInProgressHook.next = newHook;

}

}

return workInProgressHook;

}

总结

通过本文我们了解到,函数组件内的hooks函数调用是会形成一个hooks链表的,这个链表会挂载到函数组件对应fiber的memoizedState属性上。这为我们后续hooks函数的讲解奠定了基础。

欢迎扫码关注公众号,发现更多技术文章

【JS】React hooks 的基础概念:hooks链表

javascript前端react.js

阅读 46发布于 今天 12:52

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


React的秘密

最平实的语言,解读复杂React

avatar

nero

前端工程师

3.6k 声望

1.8k 粉丝

0 条评论

得票时间

avatar

nero

前端工程师

3.6k 声望

1.8k 粉丝

宣传栏

当函数组件进入render阶段时,会被renderWithHooks函数处理。函数组件作为一个函数,它的渲染其实就是函数调用,而函数组件又会调用React提供的hooks函数。初始挂载和更新时,所用的hooks函数是不同的,比如初次挂载时调用的useEffect,和后续更新时调用的useEffect,虽然都是同一个hook,但是因为在两个不同的渲染过程中调用它们,所以本质上他们两个是不一样的。这种不一样来源于函数组件要维护一个hooks的链表,初次挂载时要创建链表,后续更新的时候要更新链表。

分属于两个过程的hook函数会在各自的过程中被赋值到ReactCurrentDispatcher的current属性上。所以在调用函数组件之前,当务之急是根据当前所处的阶段来决定ReactCurrentDispatcher的current,这样才可以在正确的阶段调用到正确的hook函数。

export function renderWithHooks<Props, SecondAwrg>(

current: Fiber | null,

workInProgress: Fiber,

Component: (p: Props, arg: SecondArg) => any,

props: Props,

secondArg: SecondArg,

nextRenderLanes: Lanes,

): any {

// 区分是挂载还是更新过程,获取不同的hooks函数集合

ReactCurrentDispatcher.current =

current === null || current.memoizedState === null

? HooksDispatcherOnMount

: HooksDispatcherOnUpdate;

// 调用函数组件,

let children = Component(props, secondArg);

...

return children;

}

HooksDispatcherOnMountHooksDispatcherOnUpdate,它们内部的hooks函数是不同的实现,区别之一在于不同阶段对于hooks链表的处理是不同的。

const HooksDispatcherOnMount: Dispatcher = {

useCallback: mountCallback,

useContext: readContext,

useEffect: mountEffect,

...

};

const HooksDispatcherOnUpdate: Dispatcher = {

useCallback: updateCallback,

useContext: readContext,

useEffect: updateEffect,

...

};

认识hooks链表

无论是初次挂载还是更新,每调用一次hooks函数,都会产生一个hook对象与之对应。以下是hook对象的结构。

{

baseQueue: null,

baseState: 'hook1',

memoizedState: null,

queue: null,

next: {

baseQueue: null,

baseState: null,

memoizedState: 'hook2',

next: null

queue: null

}

}

产生的hook对象依次排列,形成链表存储到函数组件fiber.memoizedState上。在这个过程中,有一个十分重要的指针:workInProgressHook,它通过记录当前生成(更新)的hook对象,可以间接反映在组件中当前调用到哪个hook函数了。每调用一次hook函数,就将这个指针的指向移到该hook函数产生的hook对象上。例如:

const HooksExp = () => {

const [ stateHookA, setHookA ] = useState('A')

useEffect(() => { console.log('B') })

const [ stateHookC, setHookC ] = useState('C')

return <div>Hook Example</div>

}

上面的例子中,HooksExp组件内一共调用了三个hooks函数,分别是useState、useEffect、useState。那么构建hook链表的过程,可以概括为下面这样,重点关注workInProgressHook的指向变化。

调用useState('A'):

fiber.memoizedState: hookA

^

workInProgressHook

调用useEffect:

fiber.memoizedState: hookA -> hookB

^

workInProgressHook

调用useState('C'):

fiber.memoizedState: hookA -> hookB -> hookC

^

workInProgressHook

hook函数每次执行,都会创建它对应的hook对象,去进行下一步的操作,比如useReducer会在hook对象上挂载更新队列,useEffect会在hook对象上挂载effect链表。而创建hook对象的过程实际上也是hooks链表构建以及workInProgressHook指针指向更新的过程。

组件挂载

初次挂载时,组件上没有任何hooks的信息,所以,这个过程主要是在fiber上创建hooks链表。挂载调用的是mountWorkInProgressHook,它会创建hook并将他们连接成链表,同时更新workInProgressHook,最终返回新创建的hook,也就是hooks链表。

function mountWorkInProgressHook(): Hook {

// 创建hook对象

const hook: Hook = {

memoizedState: null,

baseState: null,

baseQueue: null,

queue: null,

next: null,

};

if (workInProgressHook === null) {

// workInProgressHook为null说明此时还没有hooks链表,

// 将新hook对象作为第一个元素挂载到fiber.memoizedState,

// 并将workInProgressHook指向它。

currentlyRenderingFiber.memoizedState = workInProgressHook = hook;

} else {

// workInProgressHook不为null说明已经有hooks链表,此时将

// 新的hook对象连接到链表后边,并将workInProgressHook指向它。

workInProgressHook = workInProgressHook.next = hook;

}

// 返回的workInProgressHook即为新创建的hook

return workInProgressHook;

}

我们在组件中调用hook函数,就可以获取到hook对象,例如useState:

const HooksDispatcherOnMount: Dispatcher = {

...

useState: mountState,

...

};

function mountState<S>(

initialState: (() => S) | S,

): [S, Dispatch<BasicStateAction<S>>] {

// 获取hook对象

const hook = mountWorkInProgressHook();

// 对hook对象的处理

...

return [hook.memoizedState, dispatch];

}

组件更新

在更新过程中,由于存在current树,所以workInProgress节点也就有对应的current节点。那么自然也会有两条hooks链表,分别存在于current和workInProgress节点的memorizedState属性上。鉴于此,更新过程的hooks链表构建需要另一个指针的参与:currentHook。它作为组件的workInProgressHook在上一次更新时对应的hook对象,新的hook对象可以基于它创建。另外,也可以获取到上次hook对象的一些数据,例如useEffect的前后依赖项比较,前一次的依赖项就可以通过它获得。

                currentTree

current.memoizedState = hookA -> hookB -> hookC

^

currentHook

|

workInProgress Tree |

|

workInProgress.memoizedState = hookA -> hookB

^

workInProgressHook

所以更新过程的hooks链表构建过程除了更新workInProgressHook指针的指向,还要更新currentHook的指向,以及尽可能复用currentHook来创建新的hook对象。
这个过程调用的是updateWorkInProgressHook函数:

function updateWorkInProgressHook(): Hook {

// 确定nextCurrentHook的指向

let nextCurrentHook: null | Hook;

if (currentHook === null) {

// currentHook在函数组件调用完成时会被设置为null,

// 这说明组件是刚刚开始重新渲染,刚刚开始调用第一个hook函数。

// hooks链表为空

const current = currentlyRenderingFiber.alternate;

if (current !== null) {

// current节点存在,将nextCurrentHook指向current.memoizedState

nextCurrentHook = current.memoizedState;

} else {

nextCurrentHook = null;

}

} else {

// 这说明已经不是第一次调用hook函数了,

// hooks链表已经有数据,nextCurrentHook指向当前的下一个hook

nextCurrentHook = currentHook.next;

}

// 确定nextWorkInProgressHook的指向

let nextWorkInProgressHook: null | Hook;

if (workInProgressHook === null) {

// workInProgress.memoizedState在函数组件每次渲染时都会被设置成null,

// workInProgressHook在函数组件调用完成时会被设置为null,

// 所以当前的判断分支说明现在正调用第一个hook函数,hooks链表为空

// 将nextWorkInProgressHook指向workInProgress.memoizedState,为null

nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;

} else {

// 走到这个分支说明hooks链表已经有元素了,将nextWorkInProgressHook指向

// hooks链表的下一个元素

nextWorkInProgressHook = workInProgressHook.next;

}

if (nextWorkInProgressHook !== null) {

// 依据上面的推导,nextWorkInProgressHook不为空说明hooks链表不为空

// 更新workInProgressHook、nextWorkInProgressHook、currentHook

workInProgressHook = nextWorkInProgressHook;

nextWorkInProgressHook = workInProgressHook.next;

currentHook = nextCurrentHook;

} else {

// 走到这个分支说明hooks链表为空

// 刚刚调用第一个hook函数,基于currentHook新建一个hook对象,

invariant(

nextCurrentHook !== null,

'Rendered more hooks than during the previous render.',

);

currentHook = nextCurrentHook;

const newHook: Hook = {

memoizedState: currentHook.memoizedState,

baseState: currentHook.baseState,

baseQueue: currentHook.baseQueue,

queue: currentHook.queue,

next: null,

};

// 依据情况构建hooks链表,更新workInProgressHook指针

if (workInProgressHook === null) {

currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;

} else {

workInProgressHook = workInProgressHook.next = newHook;

}

}

return workInProgressHook;

}

总结

通过本文我们了解到,函数组件内的hooks函数调用是会形成一个hooks链表的,这个链表会挂载到函数组件对应fiber的memoizedState属性上。这为我们后续hooks函数的讲解奠定了基础。

欢迎扫码关注公众号,发现更多技术文章

【JS】React hooks 的基础概念:hooks链表

以上是 【JS】React hooks 的基础概念:hooks链表 的全部内容, 来源链接: utcz.com/a/107473.html

回到顶部