使用 React 类组件时,React 有明显的生命周期方法,可以在确定的生命周期方法里做确定逻辑,自从 React Hooks 推出后,React 没有推出明确的生命周期方法,更多的是使用 useEffect
去模拟生命周期方法,比如 useMount
、useUnmount
等等。
然而经过笔者本人长时间在项目中使用 React Hooks 后,发现使用 useEffect 模拟生命周期方法不当会导致一些新问题
下面是一份来自社区的 React Hooks 的执行顺序图
以一段简单的代码解释 React Hooks 的执行顺序
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 作为执行顺序的分界线,会先执行 useState
、useMemo
、 useLayoutEffect
以及内部的变量或方法声明,后执行 useEffect
点击 click 按钮,得到了一份新结果如下
image.png通过结果可以得到,先执行 useMemo
,接着执行 useLayoutEffect
的 side effect cleanup,useEffect
的 side effect cleanup,等到 React update DOM and Refs 执行后,再执行 useLayoutEffect
、useEffect
。通过这个例子,基本上弄清楚 useEffect 的执行顺序,由此分析使用 useEffect
模拟生命周期方法不当会导致什么问题
useEffect
模拟 mount
如下useEffect(() => {
// do something
}, []);
只有当 useEffect
的 deps
参数是空数组时,该用法才等同于 mount
方法
useEffect
模拟 unmount
如下useEffect(() => {
return () => {
// do something
}
}, []);
前面的问题根本原因是 useEffect 与 Lifecycle Methods 需要解耦。如果 useEffect
的 deps
参数不是空数组,那么当前的 useEffect 不等同 mount 方法,就会造成意外的结果
社区里提供专门的 ahooks 解决这个问题,这个第三方库中有两个重要方法,分别是 useMount 和 useUnmount
useMount 的实现如下
// 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 的实现如下
// 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
const App = () => {
useMount(() => {
// ...
})
useUnMount(() => {
// ...
})
return <></>
}
使用 useMount 和 useUnmount 就不用考虑传入的依赖,实现 useEffect 与 Lifecycle Methods 解耦
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8