useCallback在多次渲染中缓存函数
大约 2 分钟
useCallback在多次渲染中缓存函数
跳过组件的重新渲染
默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件,因此每当因 theme 更改时而 ProductPage
组件重新渲染时,ShippingForm
组件也会重新渲染。
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
你可以将 ShippingForm 组件包裹在 memo
中。
如果 props 和上一次渲染时相同,那么 ShippingForm 组件将跳过重新渲染。
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
useCallback中更新state,减少依赖
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
}
期望记忆化函数具有尽可能少的依赖,当你读取 state 只是为了计算下一个 state 时,你可以通过传递 updater function 以移除该依赖
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ 不需要 todos 依赖项
// ...
}
Effect内部调用函数,频繁触发执行
有时,你想要在 Effect 内部调用函数
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
// ...
}, [createOptions]) //🔴 问题:这个依赖在每一次渲染中都会发生改变
}
**每一个响应值都必须声明为 Effect 的依赖。**但是如果将 createOptions
声明为依赖,它会导致 Effect 不断执行
解决办法:
在 Effect 中将要调用的函数包裹在 useCallback
中
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ 仅当 roomId 更改时更改
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ 仅当 createOptions 更改时更改
}
这将确保如果 roomId 相同,createOptions 在多次渲染中会是同一个函数
最好消除对函数依赖项的需求。将你的函数移入 Effect 内部
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ 无需使用回调或函数依赖!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ 仅当 roomId 更改时更改
// ...
}
优化自定义 Hook
编写一个 自定义 Hook,建议将它返回的任何函数包裹在 useCallback
中, 这确保了 Hook 的使用者在需要时能够优化自己的代码
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);
const goBack = useCallback(() => {
dispatch({ type: 'back' });
}, [dispatch]);
return {
navigate,
goBack,
};
}