💡用一个简单的技巧来优化 react 重渲染

999 阅读3分钟

为什么要叫一个简单的技巧来优化 react 的重渲染

因为在这个优化中并没有使用到 useMemouseCallback 之类的方法,只是用了一点小技巧而已

举一个例子:

一个会耗费性能的组件

import React from 'react';

function ExpensiveRender() { 
    const now = performance.now(); 
    while(performance.now() - now < 100) { 
        // 模拟一个延迟
    } 
    console.log("渲染了一个有延迟的组件"); 
    return <h3>ExpensiveRender 组件</h3>; 
} 



function App() {
    const [color, setColor] = React.useState("red");

    return (
        <div>
          <input type="text" value={color} onChange={(e) => setColor(e.target.value)} />
          <p style={{color: color}}>Hello, World!</p>
          <ExpensiveRender />
        </div>
    );
}

<ExpensiveRender /> 组件模拟一个比较耗费性能的一个组件,然后在 <App /> 组件中更新 state,导致组件重渲染,也会导致 <ExpensiveRender /> 组件的重渲染,造成了不必要的性能浪费

如下图:

很显然,这不是我们想要看到的

第一种方法:memo

虽说不用 memo 方法来解决这种问题,但是还是写下,毕竟也是一种解决方法

将之前的代码改成如下:

import { useState, memo } from "react";

// 这里使用了 memo
const ExpensiveRender = memo (() => { 
    const now = performance.now(); 
    while (performance.now() - now < 100) { 
        // 模拟一个延迟
    } 

    console.log("渲染了一个有延迟的组件"); 
    return <h3>ExpensiveRender 组件</h3>; 
}); 



function App() {
    const [color, setColor] = useState("red");

    return (
        <div>
          <input
            type="text"
            value={color}
            onChange={(e) => setColor(e.target.value)}
          />
          <p style={{ color: color }}>Hello, World!</p>
          <ExpensiveRender />
        </div>
    );
}

代码改好了,就是给 <ExpensiveRender /> 组件加一个 memo 方法

效果如下图:

嗯嗯,这样问题就解决了

但是 memo 方法并不是这篇博客主要要讲解的

第二种方法:将更改 State 的操作抽离出来

再看一次会耗费性能的组件代码,不难发现真正需要更新的只有一部分

function App() {
  const [color, setColor] = useState("red");

  return (
    <div>
      /*************这部分 start *************/
      <input
        type="text"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      <p style={{ color: color }}>Hello, World!</p>
      /*************这部分 end *************/
      <ExpensiveRender />
    </div>
  );
}

所以我们可以将这一部分抽离出来当成一个组件,这样就不会影响到其他组件

理论成立,实践开始

import { useState } from "react";

// 有延迟的组件
function ExpensiveRender() {
  const now = performance.now();
  while (performance.now() - now < 100) {
    // 模拟一个延迟
  }

  console.log("渲染了一个有延迟的组件");
  return <h3>ExpensiveRender 组件</h3>;
}

// 更改颜色组件
function Form() {
  const [color, setColor] = useState("red");

  return (
    <>
      <input
        type="text"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      <p style={{ color: color }}>Hello, World!</p>
    </>
  );
}

function App() {
  return (
    <div>
      <Form />
      <ExpensiveRender />
    </div>
  );
}

如果 color 变化,只会让 <Form /> 组件重新渲染,并不会影响到 <ExpensiveRender /> 组件,这样问题一样也是可以解决的

所以可以看到效果一样可以实现

第三种方法:内容提升

你可能觉得有了第二种方法就可以了,但是第二种方法也并不是万能的

比如需要某个效果应用到多个组件,其中也包括了耗费性能的组件

例如:

function App() {
  const [color, setColor] = useState("red");

  return (
    <div style={{ color: color }}>
      <input
        type="text"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      <p>Hello, World!</p>
      <ExpensiveRender />
    </div>
  );
}

这看起来将操作 state 的部分抽离出来并不容易

难道这貌似就又需要用到了 memo 方法了?并不是,还有一种方法

上代码:

const ColorComponent: React.FC = ({ children }) => {
  const [color, setColor] = useState("red");

  return (
    <div style={{ color: color }}>
      <input
        type="text"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
      {children}
    </div>
  );
};

function App() {
  return (
    <ColorComponent>
      <p>Hello, World!</p>
      <ExpensiveRender />
    </ColorComponent>
  );
}

这样也是可以解决问题的

为什么?

因为和 color 有关联的代码都放在了 <ColorComponent /> 组件中,和 color 没有关联的部分还是放在了 <App /> 组件中,然而这部分还是以 children 属性的形式传给了 <ColorComponent /> 组件

<ColorComponent /> 组件中的 color 变化后,理所当然的,此组件会重渲染,但是 children 属性没有变化,它还是之前的值没有改变,所以并不会访问到 children 属性上去,所以并不会影响到 children 上去

话虽如此,但是思想别太固执,并不是说只有 children 属性可以,props 中的属性也是可以的

如下:

interface IProps {
  label: React.ReactNode;
}

const ColorComponent: React.FC<IProps> = ({ label, children }) => {
  const [color, setColor] = useState("red");

  return (
    <div style={{ color: color }}>
      <input
        type="text"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />

      {label}
    </div>
  );
};

function App() {
  return <ColorComponent label={<ExpensiveRender />}></ColorComponent>;
}

这样同样是可以的,效果也一样,原理也是一样的

最后

This is not a new idea. It’s a natural consequence of React composition model. It’s simple enough that it’s underappreciated, and deserves a bit more love.

—— Dan

是的,这并不是什么新鲜玩意儿,但是也得多多注意他们,毕竟“高端的食材往往只需要采用最朴素的烹饪方式”

若有错误,欢迎评论指出

参考链接: