共计 3542 个字符,预计需要花费 9 分钟才能阅读完成。
本文是深入浅出 ahooks 源码系列文章的第五篇,该系列已整顿成文档 - 地址。感觉还不错,给个 star 反对一下哈,Thanks。
本文来摸索一下 ahooks 是怎么封装 React 的一些执行“机会”的?
Function Component VS Class Component
学习相似 React 和 Vue 这种框架,对它们生命周期的把握都是必须的,咱们须要分明的晓得咱们代码的执行程序,并且在不同的阶段执行不同操作的代码,比方须要挂载实现之后才去获取 dom 的值,否则可能会获取不到相应的值。
Class Component
应用过 React 的 Class Component 的同学,就会晓得其组件生命周期会分成三个状态:
- Mounting(挂载):已插入实在 DOM
- Updating(更新):正在被从新渲染
- Unmounting(卸载):已移出实在 DOM
简略版如下所示:
其中每个状态中还会按顺序调用不同的办法,对应的具体如下(这里不开展说):
能够通过官网提供这个网站查看详情
能够看到,会有十分多的生命周期办法,而且在不同的版本,生命周期办法还不同。
Function Component
到了 Function Component,会发现没有间接提及生命周期的概念,它是更彻底的状态驱动,它只有一个状态,React 负责将状态渲染到视图中。
对于 Function Component 来说由状态到页面渲染只有三步:
- 输出状态(prop、state)
- 执行组件的逻辑, 并在
useEffect/useLayoutEffect
中订阅副作用 - 输入 UI(Dom 节点)
重点是第二步,React 通过 useEffect/useLayoutEffect 订阅副作用。Class Component 中的生命周期都能够通过 useEffect/useLayoutEffect 来实现。它们两个的性能十分类似,咱们这里看下 useEffect。
应用 useEffect 相当于通知 React 组件须要在渲染后执行某些操作,React 将在执行 DOM 更新之后调用它。React 保障了每次运行 useEffect 的时候,DOM 曾经更新结束。这就实现了 Class Component 中的 Mounting(挂载阶段)。
当状态发生变化的时候,它可能执行对应的逻辑、更行状态并将后果渲染到视图中,这就实现了 Class Component 中的 Updating(更新阶段)。
最初通过在 useEffect 中返回一个函数,它便能够清理副作用。它的规定是:
- 首次渲染不会进行清理,会在下一次渲染,革除上一次的副作用。
- 卸载阶段也会执行革除操作。
通过返回一个函数,咱们就能实现 Class Component 中的 Unmounting(卸载阶段)。
基于 useEffect/useLayoutEffect,ahooks 做了一些封装,可能让你更加清晰的晓得你的代码执行机会。
LifeCycle – 生命周期
useMount
只在组件初始化时执行的 Hook。
useEffect 依赖如果为空,只会在组件初始化的时候执行。
// 省略局部代码
const useMount = (fn: () => void) => {
// 省略局部代码
// 单纯就在 useEffect 根底上封装了一层
useEffect(() => {fn?.();
}, []);
};
export default useMount;
useUnmount
useUnmount,组件卸载(unmount)时执行的 Hook。
useEffect 能够在组件渲染后实现各种不同的副作用。有些副作用可能须要革除,所以须要返回一个函数,这个函数会在组件卸载的时候执行。
const useUnmount = (fn: () => void) => {const fnRef = useLatest(fn);
useEffect(
// 在组件卸载(unmount)时执行的 Hook。// useEffect 的返回值中执行函数
() => () => {fnRef.current();
},
[],);
};
export default useUnmount;
useUnmountedRef
获取以后组件是否曾经卸载的 Hook。
通过判断有没有执行 useEffect 中的返回值判断以后组件是否曾经卸载。
// 获取以后组件是否曾经卸载的 Hook。const useUnmountedRef = () => {const unmountedRef = useRef(false);
useEffect(() => {
unmountedRef.current = false;
// 如果曾经卸载,则会执行 return 中的逻辑
return () => {unmountedRef.current = true;};
}, []);
return unmountedRef;
};
export default useUnmountedRef;
Effect
这里只会讲官网文档
Effect
上面的几个,有局部是定时器、防抖节流等,咱们前面的系列具体分析。
useUpdateEffect 和 useUpdateLayoutEffect
useUpdateEffect 和 useUpdateLayoutEffect 的用法跟 useEffect 和 useLayoutEffect 一样,只是会疏忽首次执行,只在依赖更新时执行。
实现思路:初始化一个标识符,刚开始为 false。当首次执行完的时候,置为 true。只有标识符为 true 的时候,才执行回调函数。
// 疏忽首次执行
export const createUpdateEffect: (hook: effectHookType) => effectHookType =
(hook) => (effect, deps) => {const isMounted = useRef(false);
// for react-refresh
hook(() => {return () => {isMounted.current = false;};
}, []);
hook(() => {
// 首次执行完时候,设置为 true,从而下次依赖更新的时候能够执行逻辑
if (!isMounted.current) {isMounted.current = true;} else {return effect();
}
}, deps);
};
useDeepCompareEffect 和 useDeepCompareLayoutEffect
用法与 useEffect 统一,但 deps 通过 lodash isEqual 进行深比拟。
通过 useRef 保留上一次的依赖的值,跟以后的依赖比照(应用 lodash 的 isEqual),并将比照后果作为 useEffect 的依赖项,从而决定回调函数是否执行。
const depsEqual = (aDeps: DependencyList, bDeps: DependencyList = []) => {return isEqual(aDeps, bDeps);
};
const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
// 通过 useRef 保留上一次的依赖的值
const ref = useRef<DependencyList>();
const signalRef = useRef<number>(0);
// 判断最新的依赖和旧的区别
// 如果相等,则变更 signalRef.current,从而触发 useEffect 中的回调
if (!depsEqual(deps, ref.current)) {
ref.current = deps;
signalRef.current += 1;
}
useEffect(effect, [signalRef.current]);
};
useUpdate
useUpdate 会返回一个函数,调用该函数会强制组件从新渲染。
返回的函数通过变更 useState 返回的 state,从而促使组件进行更新。
import {useCallback, useState} from 'react';
const useUpdate = () => {const [, setState] = useState({});
// 通过设置一个全新的状态,促使 function 组件更新
return useCallback(() => setState({}), []);
};
export default useUpdate;
总结与思考
在咱们写代码的时候须要清晰的晓得,组件的生命周期是怎么的,咱们代码的执行程序、执行的机会是怎么的。
在 Function Component 中,应用 useEffect/useLayoutEffect 实现了 Class Components 生命周期的职责。ahooks 也是基于这两个封装了常见的代码执行机会,应用这些 hook,能够让咱们的代码更加具备可读性以及逻辑更加清晰。