温故知新
useMemo
和 useCallback
useMemo
和 useCallback
是 React Hooks,用于优化函数组件的性能。尽管它们功能相似,但适用场景和作用不同。
1. useMemo
用途
- 用于 缓存计算结果。
- 避免在每次渲染时重复执行昂贵的计算。
签名
const memoizedValue = useMemo(() => computeValue, [dependency]);
computeValue
: 一个函数,返回要缓存的值。dependency
: 依赖项数组,只有在依赖发生变化时,computeValue
才会重新执行。
适用场景
- 复杂计算: 如过滤、排序、或其他需要大量计算的逻辑。
- 避免不必要的渲染: 当需要将计算结果作为子组件的
prop
传递时。
示例
import React, { useMemo, useState } from 'react';
const ExpensiveComponent = ({ numbers }) => {
const sum = useMemo(() => {
console.log('Calculating sum...');
return numbers.reduce((total, num) => total + num, 0);
}, [numbers]);
return <div>Sum: {sum}</div>;
};
const App = () => {
const [count, setCount] = useState(0);
const numbers = [1, 2, 3, 4, 5];
return (
<div>
<ExpensiveComponent numbers={numbers} />
<button onClick={() => setCount(count + 1)}>Increment: {count}</button>
</div>
);
};
输出:
- 只有当
numbers
改变时,Calculating sum...
会执行。
2. useCallback
用途
- 用于 缓存函数引用。
- 避免函数在每次渲染时被重新创建。
签名
const memoizedCallback = useCallback(() => doSomething, [dependency]);
doSomething
: 要缓存的回调函数。dependency
: 依赖项数组,只有在依赖发生变化时,回调函数才会被重新创建。
适用场景
- 防止子组件不必要的渲染: 当子组件通过
React.memo
优化且依赖于父组件传递的回调函数。 - 事件处理函数: 缓存事件处理函数,避免每次渲染都生成新的函数。
示例
import React, { useCallback, useState } from 'react';
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>Click me</button>;
});
const App = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
<div>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Increment: {count}</button>
</div>
);
};
输出:
Child rendered
只在初次渲染时执行,或handleClick
引用发生变化时执行。
3. 区别总结
特性 | useMemo | useCallback |
---|---|---|
作用 | 缓存计算结果 | 缓存函数引用 |
返回值 | 计算结果(如一个值、对象、数组) | 缓存的函数 |
适用场景 | 避免重复计算 | 避免函数重新创建 |
依赖项变化时行为 | 重新计算并返回新的结果 | 返回新的函数引用 |
4. 何时使用
-
使用
useMemo
:- 当需要优化复杂计算的性能。
- 当计算结果影响子组件的渲染。
-
使用
useCallback
:- 当需要优化子组件的性能,尤其是子组件通过
React.memo
包裹时。 - 当函数作为子组件
prop
传递,并希望避免函数重新创建。
- 当需要优化子组件的性能,尤其是子组件通过
注意事项
- 不要过度使用
useMemo
或useCallback
,如果性能优化无显著收益,会增加代码复杂度。 - 如果计算或函数轻量级,不需要提前优化。
- 两者都依赖 依赖项数组,需要确保依赖项设置正确,避免引发错误或性能问题。
useEffect
和 useLayoutEffect
在 React 中,useEffect
和 useLayoutEffect
都是用来处理副作用的 Hook,但它们的执行时机不同,适用于不同的场景。以下是它们的区别和适用场景的详细说明:
1. 执行时机
useEffect
- 执行时机: 在浏览器完成渲染后(即 异步)。
- 特点: 不会阻塞浏览器绘制,副作用的执行与渲染解耦。
- 适用场景:
- 不影响页面布局的副作用。
- 例如:数据获取(
fetch
)、订阅事件、设置计时器。
useLayoutEffect
- 执行时机: 在浏览器执行绘制之前(即 同步)。
- 特点: 会阻塞浏览器绘制,副作用的执行在 DOM 更新后、绘制前。
- 适用场景:
- 需要在 DOM 完成更新后立即操作 DOM。
- 例如:测量 DOM 元素尺寸或位置、动态调整样式。
2. 代码执行顺序
-
useEffect
顺序:- React 渲染 DOM。
- 浏览器完成绘制。
useEffect
中的代码执行。
-
useLayoutEffect
顺序:- React 渲染 DOM。
useLayoutEffect
中的代码执行。- 浏览器绘制。
3. 示例对比
useEffect
示例
以下代码设置一个计时器,更新页面内容:
import React, { useState, useEffect } from "react";
const Example = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect: DOM 已更新,页面渲染完成。");
const timer = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(timer); // 清理副作用
}, []);
return <div>Count: {count}</div>;
};
export default Example;
- 关键点:
useEffect
中的代码在页面完成渲染后运行,不影响页面的首次绘制。
useLayoutEffect
示例
以下代码测量 DOM 元素的宽度,并在绘制前更新样式:
import React, { useRef, useLayoutEffect, useState } from "react";
const Example = () => {
const [width, setWidth] = useState(0);
const divRef = useRef(null);
useLayoutEffect(() => {
console.log("useLayoutEffect: 在浏览器绘制之前运行。");
setWidth(divRef.current.offsetWidth);
});
return (
<div ref={divRef} style={{ width: `${width}px`, backgroundColor: "lightblue" }}>
Width: {width}
</div>
);
};
export default Example;
- 关键点:
useLayoutEffect
中的代码在 DOM 更新后立即运行,因此可以在浏览器绘制前修改 DOM。
4. 性能影响
-
useEffect
:- 非阻塞,不会影响页面的首次绘制,适合大多数场景。
- 更推荐默认使用
useEffect
,只有在明确需要操作 DOM 的情况下才考虑useLayoutEffect
。
-
useLayoutEffect
:- 阻塞绘制,可能会导致性能问题,尤其是在复杂的组件中。
- 仅在需要精确操作 DOM 或测量布局时使用。
5. 常见问题
为什么不直接用 useLayoutEffect
替代 useEffect
?
useLayoutEffect
会阻塞浏览器绘制,可能导致性能问题,因此只有在确实需要操作 DOM 的情况下才使用。
服务端渲染(SSR)时有什么不同?
useLayoutEffect
在服务端渲染时会产生警告,因为它需要依赖 DOM 环境。- 推荐在服务端渲染中使用
useEffect
。
6. 总结
特性 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 浏览器绘制后 | 浏览器绘制前 |
是否阻塞绘制 | 否 | 是 |
适用场景 | 数据获取、订阅、非布局相关的副作用 | 精确测量 DOM 或操作布局相关的副作用 |
性能影响 | 更高效,默认推荐使用 | 可能影响性能,仅在必要时使用 |
在实际开发中,默认使用 useEffect
,只有在需要操作或测量 DOM 时使用 useLayoutEffect
。