useMemo的替代方案

559 阅读2分钟

内容来自 Dan Abramov

如果state更新缓慢

  • 确保程序运行在生产环境中
  • 确保state没有放在比实际需要更高的位置
  • 通过react开发工具等方法检测导致二次渲染的原因
  • 在高开销的子树上或其他需要的地方包裹useMemo

可以不用useMemo的情况

如下例,当App中的color变化时,我们会重新渲染一次被我们手动大幅延缓渲染的组件。

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>;
}

这时可以用useMemo来优化,但下面介绍2种不需要使用useMemo的方法

1. 向下移动State

仔细看一下渲染代码,你会注意到返回的树中只有一部分真正关心当前的color:

let [color, setColor] = useState('red');
 ……
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p style={{ color }}>Hello, world!</p>

所以让我们把这一部分提取到Form组件中然后将state移动到该组件里:

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. 内容提升

当一部分state在高开销树的上层代码中使用时上述解法就无法奏效了。举个例子,如果我们将color放到父元素div中:

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>
  );
}

怎么办呢?我们可以将App组件分割为两个子组件。依赖color的代码就和color state变量一起放入ColorPicker组件里。 不关心color的部分就依然放在App组件中,然后以JSX内容的形式传递给ColorPicker,也被称为children属性。 当color变化时,ColorPicker会重新渲染。但是它仍然保存着上一次从App中拿到的相同的children属性,所以React并不会访问那棵子树。 因此,ExpensiveTree不会重新渲染。

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>
  );
}