React | 2020-08-23 01:42:55 1048次 1次
继续看下 hooks 中其他的一些方法原理,useEffect、useLayoutEffect 、useMemo、useCallback 等。至于 ref、context 打算和类组件的放在一起分析。
一、useEffect
useEffect 创建
useEffect 的创建指的创建数据结构,因为这个过程仅仅时挂载一些数据结构,并不会执行相关逻辑。
创建的过程分为初始加载和更新两个步骤,这两个步骤在创建数据结构时唯一的区别是是否需要挂载 destroy 方法(useEffect 中返回),初始加载时不需要这个(但是会在 flushPassiveEffectsImpl 创建好,等待更新时使用),更新时需要先执行这个方法,因为需要清除某些事件的句柄等一些操作。
同样的 useEffect 也会被挂载到 fiber 的 memoizedState 中,和 useState、useReduce 一起的,结合上篇的数据结构图。
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = mountWorkInProgressHook();
...
hook.memoizedState = pushEffect(...);
}function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = updateWorkInProgressHook();
...
// 相对于初始加载 增加了这部分的逻辑
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// 依赖没有发生变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookEffectTag, create, destroy, nextDeps);
return;
}
}
}
...
hook.memoizedState = pushEffect(...);
}通过 pushEffect 方法将返回的数据挂载到 hook 的 memoizedState,同时这份数据也会被保存到 updateQueue 中并且还会存在一条 effectList 链表,和之前文章中提到的那个不一样(但是结构一致),这里的是挂载到 updateQueue。
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
// Circular
next: (null: any),
};
// wip
let componentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
// 第一个
if (componentUpdateQueue === null) {
// 初始赋值:{ lastEffect: null }
componentUpdateQueue = createFunctionComponentUpdateQueue();
// 挂载到 wip 也就是当前fiber 的 updateQueue
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 创建一条链表放到 fiber.updateQueue 中(之前的是放到fiber上)
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}useEffect 执行
useEffect 中内部方法执行起源自 flushPassiveEffects,它会在 react render 之后执行,因为在 before mutation 阶段发起一次调度异步执行(可以追踪堆栈信息查看调用):
function commitBeforeMutationEffects() {
...
scheduleCallback(NormalPriority, () => {
flushPassiveEffects();
return null;
});
...
}然后做两件事情(这里最新版代码中已经发生变化,不过思路仍然是这样,但是存储的结构换成了数组,难得一见):
先执行上一次 render 的销毁回调
再执行当前 render 的回调
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
...
if (lastEffect !== null) {
...
do {
if ((effect.tag & tag) === tag) {
// 卸载执行
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
...
if (lastEffect !== null) {
...
do {
if ((effect.tag & tag) === tag) {
// 这个 creat 是在第一部分那里创建的
const create = effect.create;
// 执行后返回销毁回调 并挂载
effect.destroy = create();
...
}
effect = effect.next;
} while (effect !== firstEffect);
}
}这个方法为什么异步执行?与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。
其中对于 useLayoutEffect 方法而言是同步执行的,页面渲染后又立即触发这个事件,因为在 commitLifeCycles 中执行了如下方法,而不是等待调度(内容和 commitHookEffectListMount 一致):
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
二、useMemo
初始加载
和 useEffect、useState不同的是,这个方法仅需要调用执行返回结果即可,不参杂其他过程,只有初始加载和更新时会执行。
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
//依赖项
const nextDeps = deps === undefined ? null : deps;
// 首次执行传入的回调
const nextValue = nextCreate();
// 它的数据结构是 ==> [产生的值,依赖项]
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}更新
更新的时候,就是判断依赖项是否发生改变,不变则返回旧值(存储在 memoizedState 上 ),改变则重新计算:
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 取出上一次计算的值
const prevState = hook.memoizedState;
if (prevState !== null) {
// 依赖项存在,并且进行一个数据的对比, is 方法
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
// 依赖项变化则重新计算新值
const nextValue = nextCreate();
// 重新挂载最新的数据以及依赖项
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}三、useCallback
初始加载
这个方法更加过分,就是简单的将传入的函数存起来,句柄依据依赖项的变化而变化。
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}更新
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}这个方法和 useMemo 是一致的,useMemo 中也可以返回一个函数,这样就和 useCallback 的作用一致:
useMemo(()=>return fn); || || useCallback(fn);
1人赞