前言:
hooks就是解决函数组件没有state,生命周期,逻辑不能复用的一种技术方案。
renderWithHooks函数的作用是调用function组件函数的主要函数。
function组件初始化:
renderWithHooks( null, // current Fiber workInProgress, // workInProgress Fiber Component, // 函数组件本身 props, // props context, // 上下文 renderExpirationTime,// 渲染 ExpirationTime ); 对于renderwithHooks执行funtion初始化时,current是没有的,之后完成一次组件更新后,会把当前workInProgress树赋值给current树。
function组件更新:
renderWithHooks( current, workInProgress, render, nextProps, context, renderExpirationTime, );
那么renderwithHooks做了些什么呢?
在react-r econciler/src/ReactFiberHooks.js中
export function renderWithHooks(
current,
workInProgress,
Component,
props,
secondArg,
nextRenderExpirationTime,
) {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
if (workInProgress.expirationTime === renderExpirationTime) {
// ....这里的逻辑我们先放一放
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}
current fiber树:当完成一次渲染之后,会产生一个current树,current会在commit阶段替换成真实的Dom树
workInProgress fiber树:即将调和渲染的fiber树。在一次新的组件更新过程中,会从current复制一份作为workInProgress,更新完毕后,将当前的workInProgress树赋值给current树。
workInProgress.memoizedState: 在class组件中,memoizedState存放state信息,在function组件中,这里可以提前透漏一下,memoizedState在一次调和渲染过程中,以链表的形式存放hooks信息。 workInProgress.expirationTime: react用不同的expirationTime,来确定更新的优先级。 currentHook:可以理解current树上的指向的当前调度的hooks节点。 workInProgressHook:可以理解 workInProgressHook 树上指向的当前调度的hooks节点。
回到原点,renderWithHooks函数的主要作用: 1、置空workInProgress树的memoizedState和updateQueue,因为要把新的hooks信息挂载到这两个属性上,然后在组件的 commit阶段,将workInProgress树替换成current树,替换真实的DOM元素节点,并在current树保存hooks信息 2、根据当前函数是否是第一次渲染,赋予ReactCurrentDispatcher.current不同的hooks(是onMount还是onUpdate),第一次渲染组件,那么用的是HooksDispatcherOnMount hooks对象。 对于渲染后,需要更新的函数组件,则是HooksDispatcherOnUpdate对象,那么两个不同就是通过current树上是否memoizedState(hook信息)来判断的。如果current不存在,证明是第一次渲染函数组件。 3、调用Component(props,secondArg);执行函数组件,函数组件在这里真正的被执行了,然后hooks被依次执行,把hooks信息依次保存到workInProgress树上 4、将ContextOnlyDispatcher赋值给ReactCurrentDispatcher.current,也就是说我们没有在函数组件中,调用的hooks,都是ContextOnlyDispatcher对象上hooks 5、最后置空一些变量,比如currentHook,currentlyRenderingFiber,workInProgressHook
HooksDispatcherOnMount 和 HooksDispatcherOnUpdate 上面讲到了会根据函数是否是第一次渲染,给current赋值不同的hooks,接下来具体讲一下这两个hooks
const HooksDispatcherOnMount = {
useCallback: mountCallback,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
};
更新组件
const HooksDispatcherOnUpdate = {
useCallback: updateCallback,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState
};
useState、useEffect、useRef、useMemo介绍和原理:
function mountWorkInProgressHook() {
const hook: Hook = {
memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) { // 例子中的第一个`hooks`-> useState(0) 走的就是这样。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
mountWorkInProgressHook,首先每次执行一个hooks函数,都产生一个hook对象,里面保存了当前hook信息,将每个hooks以链表形式串联起来,并赋值给workInProgress的memosizedState. 为什么不能在条件语句中写hooks? 因为在下一次函数组件更新,hooks链表结构会被破坏,current树的memoizedState缓存hooks信息,和当前workInProgress不一致,如果涉及到读取state等操作,就会发生异常。 hooks通过什么来证明唯一性? 通过hooks链表顺序。
mouted阶段 hooks总结
在一个函数组件第一次渲染执行上下文过程中,每个react-hooks执行,都会产生一个hook对象,并形成链表结构,绑定在workInProgress的memoizedState属性上,然后react-hooks上的状态,绑定在当前hooks对象的memoizedState属性上。对于effect副作用钩子 ,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树构建完成,在执行每个effect副作用钩子。
hooks更新阶段 …省略 ,暂时没看
hooks挂载数据的数据结构叫做fiber 在16版本前,是直接遍历vdom,通过dom api增删改dom的方式来渲染的。引入了fiber架构后,把vdom树转换成fiber链表,然后再渲染fiber。 那么hooks挂载在哪个fiber节点呢? hooks挂载在renderwithHooks函数中的workInProgress对象上,保存在workInProgress的memorizedState上,它是一个通过next串联的链表。
useEffect、useState的实现相对要复杂一点,因为涉及到调度,hooks会把这些effect串联成一个updateQueue的链表
总结
-class 支持 state 属性和生命周期方法,而 function 组件也通过 hooks api 实现了类似的功能。 -hooks 的实现就是基于 fiber 的,会在 fiber 节点上放一个链表,每个节点的 memorizedState 属性上存放了对应的数据,然后不同的 hooks api 使用对应的数据来完成不同的功能。 -所有 hooks api 都是基于 fiber 节点上的 memorizedState 链表来存取数据并完成各自的逻辑的。 -几个简单的 hooks: useRef、useCallback、useMemo,它们只是对值做了缓存,逻辑比较纯粹,没有依赖 React 的调度。而 useState 会触发 fiber 的 schedule,useEffect 也有自己的调度逻辑。实现上相对复杂一些,我们没有继续深入。
这次只是浅学了一下,后面打算再多深入学习,这里面还有很多具体的不太明白 参考:1、React Hooks 的原理,有的简单有的不简单、 2、「react进阶」一文吃透react-hooks原理
建议先阅读2,讲得更详细,再看1熟悉一边,1讲的更概括简洁。
|