# 如何用ahooks控制时机的hook？

> 作者/来源: UCloud 运营管理员
> 发布时间: 2022-11-05T08:52:00.000Z
> 分类: 其他
> 原文链接: http://117.50.162.249:3000/yun/articles/2214

---

# 如何用ahooks控制时机的hook？

> 来源: https://www.ucloud.cn/yun/128256.html
> 作者: 3403771864
> 发布日期: 发布于2022-11-05 16:52

本篇主要和大家沟通关于ahooks ，我们可以理解为加深对 React hooks 的了解。

　　我们先说下关于抽象自定义 hooks。构建属于自己的 React hooks 工具库。

　　其实我们应该培养阅读学习源码的习惯，工具库是一个对源码阅读不错的选择。

　　注：本系列对 ahooks 的源码解析是基于v3.3.13。

　　现在就进入主题用ahooks 来封装 React要注意的“时机”？

　　Function Component VS Class Component

　　很多时候代码要讲究先后顺序，要掌握周期，且需知道在不同的阶段执行不同操作的代码，比如需要挂载完成之后才去获取 dom 的值，否则可能会获取不到相应的值。

　　Class Component

　　使用过 React 的 Class Component 的同学，就会知道其组件生命周期会分成三个状态：

　　Mounting(挂载)：已插入真实 DOM

　　Updating(更新)：正在被重新渲染

　　Unmounting(卸载)：已移出真实 DOM

　　简单版如下所示：

![1.png](https://ucloud-blog.cn-bj.ufileos.com/articles/128256/images/128256_000.png)

　　其中每个状态中还会按顺序调用不同的方法，直接看下图：

![2.png](https://ucloud-blog.cn-bj.ufileos.com/articles/128256/images/128256_001.png)

　　先要详细了解，就去官网详看。可以看到，会有非常多的生命周期方法，而且在不同的版本，生命周期方法还不同。

　　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，就可以让代码具有可读性以及逻辑更加清晰。