你为什么需要 useMount 和 useUnmount?

背景

使用 React 类组件时,React 有明显的生命周期方法,可以在确定的生命周期方法里做确定逻辑,自从 React Hooks 推出后,React 没有推出明确的生命周期方法,更多的是使用 useEffect 去模拟生命周期方法,比如 useMountuseUnmount 等等。

然而经过笔者本人长时间在项目中使用 React Hooks 后,发现使用 useEffect 模拟生命周期方法不当会导致一些新问题

React Hooks 的执行顺序

下面是一份来自社区的 React Hooks 的执行顺序图

原理分析

以一段简单的代码解释 React Hooks 的执行顺序

jsx 复制代码
import { useEffect, useMemo, useState, useLayoutEffect } from "react";

const Count = () => {
  const [count, setCount] = useState(() => {
    console.log(1);
    return 0;
  });

  const double = useMemo(() => {
    console.log(2);
    return count * 2;
  }, [count]);

  const handleClick = () => setCount((c) => c + 1);

  useEffect(() => {
    console.log(4);
    return () => {
      console.log(6);
    };
  }, [count]);

  useLayoutEffect(() => {
    console.log(3);

    return () => {
      console.log(5);
    };
  }, [count]);

  return (
    <div>
      <p>
        {count}---{double}
      </p>
      <button onClick={handleClick}>click</button>
    </div>
  );
};

export default Count;

在浏览器中第一次运行该段代码,得到下面的结果

React update DOM and Refs 作为执行顺序的分界线,会先执行 useStateuseMemouseLayoutEffect 以及内部的变量或方法声明,后执行 useEffect

点击 click 按钮,得到了一份新结果如下

通过结果可以得到,先执行 useMemo,接着执行 useLayoutEffect 的 side effect cleanup,useEffect 的 side effect cleanup,等到 React update DOM and Refs 执行后,再执行 useLayoutEffectuseEffect。通过这个例子,基本上弄清楚 useEffect 的执行顺序,由此分析使用 useEffect 模拟生命周期方法不当会导致什么问题

  • 对于生命周期挂载方法,使用 useEffect 模拟 mount 如下
jsx 复制代码
useEffect(() => {
  // do something
}, []);

只有当 useEffectdeps 参数是空数组时,该用法才等同于 mount 方法

  • 对于组件销毁的生命周期方法,使用 useEffect 模拟 unmount 如下
jsx 复制代码
useEffect(() => {
  return () => {
    // do something
  }
}, []);

useMount 和 useUnmount

前面的问题根本原因是 useEffect 与 Lifecycle Methods 需要解耦。如果 useEffectdeps 参数不是空数组,那么当前的 useEffect 不等同 mount 方法,就会造成意外的结果

社区里提供专门的 ahooks 解决这个问题,这个第三方库中有两个重要方法,分别是 useMount 和 useUnmount

useMount 的实现如下

ts 复制代码
// useMount.ts

import { useEffect } from 'react'

const useMount = (fn: () => void) => {
  if (!isFunction(fn)) {
    console.error(`useMount: parameter \`fn\` expected to be a function, but got "${typeof fn}".`)
  }

  useEffect(() => {
    fn?.()
  }, [])
}

export default useMount

useUnmount 的实现如下

ts 复制代码
// useUnMount.ts

import { useEffect, useRef } from 'react'

export default function useUnmount(fn: () => void): void {
  if (!isFunction(fn)) {
   console.error(`useUnmount: parameter \`fn\` expected to be a function, but got "${typeof fn}".`)
  }

  const ref = useRef(fn)

  useEffect(
    (): (() => void) => (): void => {
      ref.current?.()
    },
    []
  )
}

function isFunction(fn: unknown): fn is Function {
  return typeof fn === 'function'
}

在项目中这样使用 useMount 和 useUnMount

ts 复制代码
const App = () => {
  useMount(() => {
    // ...
  })

  useUnMount(() => {
    // ...
  })

  return <></>
}

使用 useMount 和 useUnmount 就不用考虑传入的依赖,实现 useEffect 与 Lifecycle Methods 解耦

相关推荐
Setsuna_F_Seiei5 小时前
前端转型 Agent 开发 04 之 MCP 与 Skill(赋予 Agent 更广工作能力)
前端·agent
刘发财7 小时前
前端2秒生成500页矢量PDF,rust真的强到没朋友
前端·javascript·rust
郑州光合科技余经理7 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
梦想平凡8 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
yume_sibai9 小时前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
计算机魔术师10 小时前
特朗普上台打给黄仁勋:AI末日论是骗局,我们绝不让它发生
前端
troy12810 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
计算机魔术师10 小时前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
kyriewen11 小时前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员
IT_陈寒12 小时前
Java里用Stream.parallel()翻车实录,这性能还不如单线程
前端·人工智能·后端