本文正在参加「金石计划」
const cachedValue = useMemo(calculateValue, dependencies)
-
在重新渲染之间(组件里面)缓存计算结果
-
calculateValue 纯函数,没有参数,返回你想缓存的数据
-
初始渲染调用,dependencies 没有改变下次渲染会返回缓存的数据, 改变了重新执行 calculateValue 返回数据
-
首次渲染是不会缓存数据的
使用
除了使用 useMemo,一些其他建议
-
组件作为 children 传递时
-
减少 state 使用和提升
-
渲染逻辑保持纯净
-
避免在 Effect 中更新 state
-
尽量减少 Effect dependencies
子组件跳过重新渲染
-
父组件更新一些 state,子组件没有用到这些 state
export default function TodoList({ todos, tab, theme }) { // ... return ( <div className={theme}> <List items={visibleTodos} /> </div> ); } import { memo } from 'react'; const List = memo(function List({ items }) { // ... }); -
子组件 props 没有改变,不会重新渲染
export default function TodoList({ todos, tab, theme }) { // Every time the theme changes, this will be a different array... const visibleTodos = filterTodos(todos, tab); return ( <div className={theme}> {/* ... so List's props will never be the same, and it will re-render every time */} <List items={visibleTodos} /> </div> ); } export default function TodoList({ todos, tab, theme }) { // Tell React to cache your calculation between re-renders... const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] // ...so as long as these dependencies don't change... ); return ( <div className={theme}> {/* ...List will receive the same props and can skip re-rendering */} <List items={visibleTodos} /> </div> ); }
缓存依赖组件里面定义的对象
useMemo 依赖组件内部一个对象时,也要把这个对象缓存或者把这个对象放在同一个 useMemo 里面
```JavaScript
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ...
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ...
```
缓存函数
-
用
useMemo记忆一个函数,算函数必须返回另一个函数export default function Page({ productId, referrer }) { const handleSubmit = useMemo(() => { return (orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails }); }; }, [productId, referrer]); return <Form onSubmit={handleSubmit} />; } -
使用 useCallback
export default function Page({ productId, referrer }) { const handleSubmit = useCallback((orderDetails) => { post('/product/' + productId + '/buy', { referrer, orderDetails }); }, [productId, referrer]); return <Form onSubmit={handleSubmit} />; }
问答
-
每次渲染 calculation 运行两次
calculation 不是纯函数
-
useMemo 应该返回对象,但是返回 undefined
calculation 写错了
-
每次渲染组件 calculation 都会执行
没有指定 dependencies,或者 dependencies 返回了一个新的数据
-
循环中使用 useMemo
提取循环内容为一个组件,在组件内部使用 useMemo, 可以缓存某个数据或者整个组件
function ReportList({ items }) { return ( <article> {items.map(item => { // 🔴 You can't call useMemo in a loop like this: const data = useMemo(() => calculateReport(item), [item]); return ( <figure key={item.id}> <Chart data={data} /> </figure> ); })} </article> ); } function ReportList({ items }) { return ( <article> {items.map(item => <Report key={item.id} item={item} /> )} </article> ); } function Report({ item }) { // ✅ Call useMemo at the top level: const data = useMemo(() => calculateReport(item), [item]); return ( <figure> <Chart data={data} /> </figure> ); } function ReportList({ items }) { // ... } const Report = memo(function Report({ item }) { const data = calculateReport(item); return ( <figure> <Chart data={data} /> </figure> ); });
Memo
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
- props 没有改变跳过重新渲染
- 组件内部 state 改变还是会重新渲染
- context 改变还是会重新渲染
// name 没有变化,Greeting 就不会渲染
// greeting 变化,Greeting 还是重新渲染
// theme 变化,Greeting 还是重新渲染
import { memo, createContext, useContext, useState } from "react";
const ThemeContext = createContext(null);
export default function MyApp() {
const [name, setName] = useState("");
const [address, setAddress] = useState("");
const [theme, setTheme] = useState('dark');
function handleClick() {
setTheme(theme === 'dark' ? 'light' : 'dark');
}
return (
<ThemeContext.Provider value={theme}>
<button onClick={handleClick}>Switch theme</button>
<label>
Name{": "}
<input value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
Address{": "}
<input value={address} onChange={(e) => setAddress(e.target.value)} />
</label>
<Greeting name={name} />
</ThemeContext.Provider>
);
}
const Greeting = memo(function Greeting({ name }) {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
const theme = useContext(ThemeContext);
const [greeting, setGreeting] = useState("Hello");
return (
<>
<h3 className={theme}>
{greeting}
{name && ", "}
{name}!
</h3>
<GreetingSelector value={greeting} onChange={setGreeting} />
</>
);
});
function GreetingSelector({ value, onChange }) {
return (
<>
<label>
<input
type="radio"
checked={value === "Hello"}
onChange={(e) => onChange("Hello")}
/>
Regular greeting
</label>
<label>
<input
type="radio"
checked={value === "Hello and welcome"}
onChange={(e) => onChange("Hello and welcome")}
/>
Enthusiastic greeting
</label>
</>
);
}
-
最小化 props 改变
- props 是对象父组件传递的时候使用 useMemo 包裹
- 传递最小化需要的数据
- 使用能够推导出来的 props 而不是整个对象传递
- 传递函数把它放在组件外部或者使用 useCallback
-
自定义比较函数方法
影响性能而且并不一定正确
const Chart = memo(function Chart({ dataPoints }) { // ... }, arePropsEqual); function arePropsEqual(oldProps, newProps) { return ( oldProps.dataPoints.length === newProps.dataPoints.length && oldProps.dataPoints.every((oldPoint, index) => { const newPoint = newProps.dataPoints[index]; return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y; }) ); }