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的源码项目,进行源码目录,工程化,测试等分析和使用,敬请期待,更多的交流关注喵爸的小作坊

相关推荐
JELEE.10 分钟前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl2 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫4 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友4 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理6 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻6 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
mapbar_front6 小时前
在职场生存中如何做个不好惹的人
前端
牧杉-惊蛰7 小时前
纯flex布局来写瀑布流
前端·javascript·css
一袋米扛几楼988 小时前
【软件安全】什么是XSS(Cross-Site Scripting,跨站脚本)?
前端·安全·xss
向上的车轮8 小时前
Actix Web适合什么类型的Web应用?可以部署 Java 或 .NET 的应用程序?
java·前端·rust·.net