请删掉99%的useMemo
阿里妹导读
你的useMemo真正为你的项目带来了多少性能上的优化?由于useMemo和useCallback类似,所以本文全文会在大部分地方以useMemo为例,部分例子使用useCallback帮助大家更好的理解两个hooks。
不知道大家在什么情况下会考虑使用useMemo,你是不是这么想的?
你为什么要用useMemo?
啥是useMemo?
1.跳过代价昂贵的重新计算
2.跳过组件的重渲染
3.记忆另一个Hook的依赖
核心源码
只挑重点,转换为白话,减少源码带来的恐惧感,请各位客官放心食用~
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
// 省略部分
...
// $FlowFixMe[incompatible-use] found when upgrading Flow
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
为什么一个组件会重渲染它自己?
const Page = () => <Item />;
const App = () => {
const [state, setState] = useState(1);
return (
<div>
<button onClick={() => setState(state + 1)}>
click to re-render {state}
</button>
// Page是子组件,且没有props,里面也没有state
<Page />
</div>
);
};
const Page = () => <Item />;
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
return (
// ... same code as before
<PageMemoized />
);
};
它们被作为 attributes ,直接地或作为依赖树的上层,被传递到某个 DOM 上;
它们被作为 props,直接地或作为依赖树的上层,被传递到某个未被缓存的组件上;
它们被作为 props,直接地或作为依赖树的上层,被传递到某个组件上,而那个组件至少有一个 prop 未被缓存;
避免每次渲染时进行昂贵的计算
这里暂时使用这篇文章计算的数据:https://www.developerway.com/posts/how-to-use-memo-use-callback
计算代码:https://codesandbox.io/s/measure-without-memo-tnhggk?file=/src/page.tsx
const Item = ({ country }: { country: Country }) => {
return <button>{country.name}</button>;
};
const List = ({ countries }) => {
// sorting list of countries here
const sortedCountries = orderBy(countries, 'name', sort);
return (
<>
{sortedCountries.map((country) => (
<Item country={country} key={country.id} />
))}
</>
);
};
渲然后的按钮列表
const List = ({ countries }) => {
const content = useMemo(() => {
const sortedCountries = orderBy(countries, 'name', sort);
return sortedCountries.map((country) => <Item country={country} key={country.id} />);
}, [countries, sort]);
return content;
};
常见的错误用法(重点)
初级
const Component = () => {
const onClick = useCallback(() => {
/* do something */
}, []);
return <button onClick={onClick}>Click me</button>
};
const Item = () => <div> ... </div>
const MemoItem = React.memo(Item)
const Component = () => {
const onClick = useCallback(() => {
/* do something */
}, []);
return <MemoItem onClick={onClick} value={[1,2,3]}/>
};
中级
const Item = () => <div> ... </div>
const MemoItem = React.memo(Item)
const Component = () => {
const onClick = useCallback(() => {
/* do something */
}, []);
return
<MemoItem onClick={onClick}>
<div>something</div>
</MemoItem>
};
// 以下写法均等价,也就是说在props中传递children,和直接children嵌套是一致的
React.createElement('div',{
children:'Hello World'
})
React.createElement('div',null,'Hello World')
<div>Hello World</div>
const Item = () => <div> ... </div>
const MemoItem = React.memo(Item) // useless
const Component = () => {
const onClick = useCallback(() => { //useless
/* do something */
}, []);
return
<MemoItem
onClick={onClick}
children={<div>something</div>}
/>
};
高级
const Item = () => <div> ... </div>
const Child = () => <div>sth</div>
const MemoItem = React.memo(Item)
const MemoChild = React.memo(Child)
const Component = () => {
const onClick = useCallback(() => {
/* do something */
}, []);
return (
<MemoItem onClick={onClick}>
<MemoChild />
</MemoItem>
)
};
const child = <MemoChild />;
const child = React.createElement(MemoChild,props,childen);
const child = {
type: MemoChild,
props: {}, // same props
... // same interval react stuff
}
终极解决思路
const Child = () => <div>sth</div>
const MemoItem = React.memo(Item)
const Component = () => {
const onClick = useCallback(() => {
/* do something */
}, []);
const child = useMemo(()=> <Child /> ,[])
return (
<MemoItem onClick={onClick}>
{child}
</MemoItem>
)
};
你应该在所有地方加上useMemo吗?
你明确知道这个计算非常的昂贵,而且它的依赖关系很少改变。
如果当前的计算结果将作为memo包裹组件的props传递。计算结果没有改变,可以利用useMemo缓存结果,跳过重渲染。
当前计算的结果作为某些hook的依赖项。比如其他的useMemo/useEffect依赖当前的计算结果。
没了useMemo,我不知道怎么办了
例子
import { useState } from 'react';
export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
function ExpensiveTree() {
let now = performance.now();
while (performance.now() - now < 100) {
// Artificial delay -- do nothing for 100ms
}
return <p>I am a very slow component tree.</p>;
}
解决方案1:状态下移
export default function App() {
let [color, setColor] = useState('red');
return (
<div>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
export default function App() {
return (
<>
<Form />
<ExpensiveTree />
</>
);
}
function Form() {
let [color, setColor] = useState('red');
return (
<>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>
</>
);
}
解决方案2:内容提升
export default function App() {
let [color, setColor] = useState('red');
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
export default function App() {
return (
<ColorPicker>
<p>Hello, world!</p>
<ExpensiveTree />
</ColorPicker>
);
}
function ColorPicker({ children }) {
let [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
{children}
</div>
);
}
总结
为什么一定要移除?
图源自:Dominik【ReactJs • TypeScript • Father of two】
React团队的看法
原视频链接:https://www.youtube.com/watch?v=lGEMwh32soc&t=620s
最后
「不知道行不行,但是感觉这里需要memo一下,用了指定能优化,就算不行也没啥影响」
「需要对数据处理,量好像还挺多,且不怎么需要变化,符合memo的能力」
「数据处理起来很麻烦,写方法不乐意,memo好像可以帮我套一层用方法的写法返回数据,真不戳」
参考文章:
这些文章都非常优秀,可以帮助您更好的了解useMemo的正确使用
https://react.dev/reference/react/useMemo
https://react.dev/reference/react/memo#memo
https://tkdodo.eu/blog/the-uphill-battle-of-memoization
https://www.developerway.com/posts/how-to-use-memo-use-callback
https://overreacted.io/before-you-memo/
https://kentcdodds.com/blog/what-is-jsx
https://kentcdodds.com/blog/optimize-react-re-renders
微信扫码关注该文公众号作者