ahooks生命周期相关hook 源码解读

上期我们主要介绍了useMount源码和一些前置的知识点,在ahooks中生命周期相关hook还剩下useUnmountuseUnmountedRef

上期留下的问题

上期传送门,useMount的实现为什么不用useLayoutEffect,我个人总结还是同步和异步的区别,如果使用useLayoutEffect实现的话,如果useMount的fn中,如果存在重度计算等代码就会存在阻塞首屏渲染的风险。其次的话,如果在SSR的场景,React也是不支持useLayoutEffect的

为什么上期useUnmount的实现是有问题的

首先我们看下

php 复制代码
const useUnmount = (fn: () => void) => {
  useEffect(
    () => () => {
      fn?.();// 就这?
    },
    [],
  );
};

可能暂时一眼还看不出问题所在,不如我们自己在demo中跑一下看看

javascript 复制代码
import { useEffect, useState } from "react";

const useUnmount = (fn) => {
  useEffect(
    () => () => {
      fn?.(); // 就这?
    },
    [],
  );
};
const Child = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  useUnmount(() => {
    console.log("count:", count);
  });
  return <div>{count}</div>;
};

export default function Test() {
  const [flag, setFlag] = useState(false);

  return (
    <div>
      <button
        onClick={() => {
          setFlag(!flag);
        }}
      >
        点我
      </button>
      {!!flag ? <Child /> : null}
    </div>
  );
}

大家脑海里面可以想下组件卸载的时候log的count值是多少,界面上的count值是多少?

如果觉得log中count值是0的同学,恭喜你们get到了,这里就存在一个闭包的问题,如何解决呢?我们看ahooks的源码是如何解决的

useUnmount

javascript 复制代码
import { useEffect } from 'react';
import useLatest from '../useLatest';
import { isFunction } from '../utils';
import isDev from '../utils/isDev';

const useUnmount = (fn: () => void) => {
  if (isDev) {
    if (!isFunction(fn)) {
      console.error(`useUnmount expected parameter is a function, got ${typeof fn}`);
    }
  }

  const fnRef = useLatest(fn);

  useEffect(
    () => () => {
      fnRef.current();
    },
    [],
  );
};

export default useUnmount;

这里使用了useLatest来保证得到最新值,至于useLatest又是怎么实现的,我们到对应的章节来讲,现在大家只要知道这样能解决刚才说的闭包问题就好了,我们改下代码,再试一下就能发现

image-20231206130223438

Wow!?变成1了。但是,你是不是发现界面上的值也一直是1,为什么没有累加上去呢?

思考一下。嗯。

对咯,这个Effect里面也是有闭包问题的,不妨你在定时器内部也log下count,是不是一直是0

具体原因就牵扯到useEffct的源码实现了,我们后面在讲,这里不展开了。

解决办法呢也是可以用下刚才提到的useLatest

scss 复制代码
 const ref = useLatest(count);
 
 useEffect(() => {
   const interval = setInterval(() => {
          console.log("count:", count);
          console.log("ref:", ref);
          setCount(ref.current + 1);
   }, 1000);
   return () => clearInterval(interval);
 }, []);

useUnmountRef

本篇最后我们再聊最后一个ahooks中跟生命周期有关的一个hook,useUnmountRef

作用就是给你一个标记,让你在使用的时候知道当前组件是否已经被卸载

useUnmountRef源码

ini 复制代码
import { useEffect, useRef } from 'react';

const useUnmountedRef = () => {
  const unmountedRef = useRef(false);
  useEffect(() => {
    unmountedRef.current = false;
    return () => {
      unmountedRef.current = true;
    };
  }, []);
  return unmountedRef;
};

export default useUnmountedRef;

这里牵扯到原生hook,useRef,不在赘述,简单理解就是通过useRef,分别在挂载和卸载阶段修改对应的值

demo如下

javascript 复制代码
/**
 * title: Default usage
 * desc: unmountedRef.current means whether the component is unmounted
 *
 * title.zh-CN: 基础用法
 * desc.zh-CN: unmountedRef.current 代表组件是否已经卸载
 */

import { useBoolean, useUnmountedRef } from 'ahooks';
import { message } from 'antd';
import React, { useEffect } from 'react';

const MyComponent = () => {
  const unmountedRef = useUnmountedRef();
  useEffect(() => {
    setTimeout(() => {
      if (!unmountedRef.current) {
        message.info('component is alive');
      }
    }, 3000);
  }, []);

  return <p>Hello World!</p>;
};

export default () => {
  const [state, { toggle }] = useBoolean(true);

  return (
    <>
      <button type="button" onClick={toggle}>
        {state ? 'unmount' : 'mount'}
      </button>
      {state && <MyComponent />}
    </>
  );
};

好了,本期内容就到这里。

个人感觉一开始这3个hook的实现是相对比较容易的,但也有些细微的点值得大家去注意的。后面我们会讲下ahooks中State相关的hook及源码实现。

下期预告

我们将在下期,clone下ahooks的源码项目,进行源码目录,工程化,测试等分析和使用,敬请期待,更多的交流关注喵爸的小作坊

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试