avatar

React 简单优化


优化的原因

react 里的一个基本常识是 re-render:当一个组件里的某个状态改变的时候,他会重新渲染,也就是重新执行所有组件代码,包括它的子组件。

所以会带来很多不必要的重新渲染。

useMemo

useMemo 的基本理念是,它允许我们在渲染之间 "记住 "一个计算值

使用的场景举例

 1function App() {
 2  const [selectedNum, setSelectedNum] = React.useState(100);
 3  const time = useTime();
 4
 5  const allPrimes = React.useMemo(() => {
 6    const result = [];
 7
 8    for (let counter = 2; counter < selectedNum; counter++) {
 9      if (isPrime(counter)) {
10        result.push(counter);
11      }
12    }
13
14    return result;
15  }, [selectedNum]);
16
17   return (
18    <-省略dom->
19   )

这里 App 组件里有一个状态 selectedNum,还有一个自定义 hook 导出的状态 time。假如不使用 useMemo 包裹 allPrimes,那么每当 time 变化的时候,就会重新执行 allPrimses 里面的代码,造成浪费;然而我们的 allPrimes 其实只跟 selectedNum 相关,所以我们这里用 useMemo 缓存函数包裹后,里面的代码就只会在 selectedNum 变化时执行,而不受 App 组件的 re-render 影响。

当然,这里也可以有其他的写法,我们可以把 allPrimes 和 selectedNum 包括它的 dom 合并成一个子组件,time 和它的 dom 也合并成一个子组件

1function App(){
2
3  return(
4  	<Time />
5    <AllPrimes />
6  )
7}

这样,他们的状态隔离开,也可以实现当 time 里面的状态改变的时候,不会影响别的代码去 re-render。

但是,有时候,我们反而是需要把状态放到外部组件的,因为可能有别的组件也同样依赖这个状态。

1function App(){
2  const time = useTime();
3  return(
4  	<Time time={time} />
5    <AllPrimes />
6  )
7}

这样的话,当 App 组件的 time 状态变化的时候,其实同样会使 Time 组件和 AllPrimes 组件 re-render。这个时候,就可以使用 React.memo 来优化

1const PureAllPrimes = React.memo(AllPrimes);
2function App(){
3  const time = useTime();
4  return(
5  	<Time time={time} />
6    <PureAllPrimes />
7  )
8}

React.memo 可以把一个组件变成 pure component,pure component 只有在 props 变化的时候,才会 re-render;

当然,也可以在 AllPrimes 组件里,导出的时候去使用 React.memo

1export default React.memo(AllPrimes);

我们来举另外一个例子

Boxes 是一个 pure component,只有 boxes 变化的时候,才会 re-render;

 1function Boxes({ boxes }) {
 2  return (
 3    <div className="boxes-wrapper">
 4      {boxes.map((boxStyles, index) => (
 5        <div key={index} className="box" style={boxStyles} />
 6      ))}
 7    </div>
 8  );
 9}
10
11export default React.memo(Boxes);

App 组件里引用了 Boxes,并声明常量 boxes 传入 Boxes 组件里

 1import Boxes from './Boxes';
 2
 3function App() {
 4  const [name, setName] = React.useState('');
 5  const [boxWidth, setBoxWidth] = React.useState(1);
 6  const boxes = [
 7    { flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
 8    { flex: 3, background: 'hsl(260deg 100% 40%)' },
 9    { flex: 1, background: 'hsl(50deg 100% 60%)' },
10  ];
11
12  return (
13    <>
14      <Boxes boxes={boxes} />
15      <-省略dom->
16    </>
17  );
18}

当状态 name 变化的时候,我们期望只是 re-render App 组件,Boxes 组件不 re-render,然而 Boxes 组件仍然 re-render 了。这是因为,App 组件 re-render 的时候,重新生成了 boxes 变量,尽管是同样的值,但不是一个引用,所以导致了 Boxes 组件的 re-render

要解决这个问题,只需要使用 useMemo 包裹一下即可

1const boxes = React.useMemo(() => {
2  return [
3    { flex: boxWidth, background: "hsl(345deg 100% 50%)" },
4    { flex: 3, background: "hsl(260deg 100% 40%)" },
5    { flex: 1, background: "hsl(50deg 100% 60%)" },
6  ];
7}, [boxWidth]);

UseCallback

它跟 useMemo 是一样的,都是缓存函数,但是他缓存的不是值,而是函数;

使用场景举例

MegaBoost 是一个 pure component,接受一个 callback 回调函数,只有当 callback 变化的时候才会 re-render

 1function MegaBoost({ handleClick }) {
 2  console.log("Render MegaBoost");
 3
 4  return (
 5    <button className="mega-boost-button" onClick={handleClick}>
 6      MEGA BOOST!
 7    </button>
 8  );
 9}
10
11export default React.memo(MegaBoost);

App 组件里引用了 MegaBoost

 1function App() {
 2  const [count, setCount] = React.useState(0);
 3  function handleMegaBoost() {
 4    setCount((currentValue) => currentValue + 1234);
 5  }
 6
 7  return (
 8    <>
 9      Count: {count}
10      <button
11        onClick={() => {
12          setCount(count + 1);
13        }}
14      >
15        Click me!
16      </button>
17      <MegaBoost handleClick={handleMegaBoost} />
18    </>
19  );
20}
21
22export default App;

App 里声明了一个状态 count,一个函数 handleMegaBoost 并传入 MegaBoost 组件。

当状态 count 变化的时候,会触发 App 组件的 re-render,并且重新生成一个新的 handleMegaBoost 传入 MegaBoost 组件,导致 MegaBoost 组件的 re-render。

怎么解决这个问题呢?很简单,使用缓存函数即可

1const handleMegaBoost = React.useMemo(() => {
2  return function () {
3    setCount((currentValue) => currentValue + 1234);
4  };
5}, []);

但是,一般我们更倾向于使用 useCallback

1const handleMegaBoost = React.useCallback(() => {
2  setCount((currentValue) => currentValue + 1234);
3}, []);

总结

  1. 组件里一些”经过复杂的逻辑计算而得到某个值“需要使用 Usememo 包裹,保证组件在 re-render 的时候不去重复计算
  2. 父子组件的场景,子组件尽量用 React.memo 包裹成 pure component,避免父组件的 re-render 导致子组件跟着一起 re-render
  3. 父子组件的场景,父组件声明变量传入子组件的时候,尽量用 useMemo 包裹,避免父组件 re-render 导致重新生成变量,而进一步导致子组件的 re-render
  4. 父子组件的场景,副组件声明 callback 传入子组件的时候,尽量使用 useCallback 包裹,避免父组件 re-render 导致重新生成 callback,而进一步导致子组件的 re-render

引用链接

引用链接

评论列表:

暂无评论 😭