useMemo: 缓存值,在每次重渲染之间缓存数据,避免组件重新渲染
useCallback: 缓存函数,在每次重渲染之间缓存数据,避免组件重新渲染
例如:
// bad
// 比较”重渲染之前的 a“ 和 ”重渲染之后的 a“,结果都会是 false,所以被 useEffect 包裹的函数也将会在每次重渲染的过程中触发调用
const Component = () => {
const a = { test: 1 };
useEffect(() => {
// "a" 将会在每次重渲染时被比较
}, [a]);
// 你剩下的代码
};
// good
const Component = () => {
// 在每次重渲染之间保存 a
const a = useMemo(() => ({ test: 1 }), []);
useEffect(() => {
// 只有当 a 的值真实发生改变时才会触发
}, [a]);
// 你剩下的代码
};
const Component = () => {
// 在每次重渲染之间保存 onClick 方法
const fetch = useCallback(() => {
console.log('fetch some data here');
}, []);
useEffect(() => {
// 只有当 fetch 的值真的发生该表的时候才会触发
fetch();
}, [fetch]);
// 你剩下的代码
};
在这里,需要记住的最重要的一件事是,useMemo
和 useCallback
只有在重渲染的过程中才有用
当 state 或者 prop 发生变化的时候,组件就会重渲染自己,当一个组件重渲染它自己的时候,它也会同时重渲染它的 children
例如:
// 父组件
const App = () => {
const [state, setState] = useState(1);
return (
<div className="App">
<button onClick={() => setState(state + 1)}>
click to re-render {state}
</button>
<br />
<Page />
</div>
);
};
// 子组件
const Page = () => <Item />;
当App中的state改变的时候,会导致App重新渲染,并且会重渲染它所有的children,及时Page
组件甚至没有 props!也没有 state。
当我们使用React.memo缓存组件时
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
return (
... // same code as before
<PageMemoized />
);
};
可以解决重渲染问题。
让我们试想一下,如果给page传递一个事件呢?
const App = () => {
const [state, setState] = useState(1);
const onClick = () => {
console.log('Do something on click');
};
return (
// 不管 onClick 有没有缓存,page 都会重渲染
<Page onClick={onClick} />
);
};
App和Page都会重渲染,onClick有没有包裹useCallback并不重要
即使我们把Page缓存起来
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
const onClick = () => {
console.log('Do something on click');
};
return (
// 因为 onClick 没有缓存,PageMemoized 「将会」重渲染
<PageMemoized onClick={onClick} />
);
};
App
会重渲染,React 会在它的 children 中发现 PageMemoized
组件,并意识到它被 React.memo
方法包裹,这会打断重渲染链条,React 会事先检查这个组件的 props 是否有变化。在这个例子里,既然 onClick
是一个未被缓存的函数,props 比较的结果就会是 false
,那么 PageMemoized
组件就会重渲染它自己。
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
const onClick = useCallback(
() => { console.log('Do something on click'); },
[]
);
return (
// PageMemoized 因为 onClick 被缓存了,将「不会」重渲染
<PageMemoized onClick={onClick} />
);
};
现在,当 React 检查 PageMemoized
的 props 有无变化时,onClick
将会保持不变,只有在这种情况下, PageMemoized
才不会重渲染。
这个时候,如果我再给PageMemoized组件传递一个属性值呢?
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
const onClick = useCallback(
() => { console.log('Do something on click'); },
[]
);
return (
// 因为 value 没有被缓存,page 「将会」重渲染
<PageMemoized onClick={onClick} value={[1, 2, 3]} />
);
};
由于value没有缓存,所以PageMemoized还是会重新渲染
所以可以得出结论:只有在唯一的一种场景下,缓存 props 才是有意义的---当组件的每一个 prop,以及组件本身被缓存的时候。
如果组件代码里有以下情形,我们可以毫无心理负担地删掉 useMemo
和 useCallback
:
- 它们被作为 attributes ,直接地或作为依赖树的上层,被传递到某个 DOM 上
- 它们被作为 props,直接地或作为依赖树的上层,被传递到某个未被缓存的组件上
- 它们被作为 props,直接地或作为依赖树的上层,被传递到某个组件上,而那个组件至少有一个 prop 未被缓存