React 组件既然有状态, 就肯定需要根据状态的进行交互和同步, 在 React Hooks 使用 useEffect
和 useLayoutEffect
函数进行同步。
吐槽一点, Effect 虽然是被用作同步的, 但是 Effect 本身的字面意思和同步没有任何关系, 导致很多中文文档中都翻译成
效应
,副作用
之类的, 感觉有点儿不伦不类, 至少和同步不搭边。 这一点, React 官方也没有一个说法, 至今没有一个信达雅
的中文翻译。
useEffect
的函数定义是这样的:
function useEffect(
effect: () => (void | Destructor),
deps?: ReadonlyArray<unknown>
): void;
我的理解是:
unknown
;常见用法有:
useEffect
的依赖项为空数组, 相当于和函数组件的生命周期进行同步, 即当控件加载之后和销毁时执行同步;
useEffect(() => {
// 当控件加载完成时执行
function onClick() {
console.log('click');
}
document.body.addEventListener('click', onClick);
// 返回一个清理函数, 在控件销毁时执行
return () => {
document.body.removeEventListener('click', onClick);
};
}, []);
useEffect
的依赖项不为空, 任意依赖项发生变化时, 会自动执行同步;
useEffect(() => {
// 当 state1 或者 state2 发生变化时, 会执行同步, 调用这个函数;
console.log(`effect for {state1: ${state1}, state2: ${state2}}`);
// 返回一个清理函数, 再次变化之前, 会调用这个清理函数;
return () => {
console.log(`cleanup for {state1: ${state1}, state2: ${state2}}`);
};
}, [state1, state2]);
useLayoutEffect
是 useEffect
的另一个版本, 在浏览器重绘界面之前调用, 用法和 useEffect
一样, 但是会影响性能。
useLayoutEffect
典型的用法是在浏览器重绘之前测量布局。
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 现在不知道真正的高度
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 计算得到高度
}, []);
// ...使用计算得到的高度进行后面的渲染工作 ...
}
除非有必要, 请尽可能地使用
useEffect
。
个人觉得, 一个 Effect 应当只做一件事情, 不要在一个 Effect 中做太多事情, 可以使用多个 Effect 。
不建议的用法:
useEffect(() => {
doSomethingA();
doSomethingB();
doSomethingC();
}, []);
建议的用法, 因为这样不仅更加清晰, 而且更加有利于在重构时提取控件。
useEffect(() => {
doSomethingA();
}, []);
useEffect(() => {
doSomethingB();
}, []);
useEffect(() => {
doSomethingC();
}, []);