你可能并不需要useMemo

1,943 阅读1分钟

前言

在 React 组件中,我们通常会使用memouseMemo来优化组件性能,本质上是为了避免组件不必要重的重复渲染,这里分享两种不同的技巧,也能够达到避免重复渲染的效果:

问题

如下,是一个严重影响渲染性能的组件:

import React, { useState } from 'react';

export default function App() {
  const [text, setText] = useState('one')
  return (
    <div>
      <p onChange={() => setText('two')}>{text}</p>
      <ExpensiveItem />
    </div>
  );
}

function ExpensiveItem() {
  doExpensiveAction = () => {
    {/* 这里做一些影响性能的操作 */ }
  }
  return <div>我是一个耗费性能的组件:{doExpensiveAction()}</div>
}

问题就是当App中的text变化时,React会重新渲染影响性能的<ExpensiveItem />组件。我们可以直接用memouseMemo来优化,但是现在已经有很多这方面的文章了,这里说下另外两种解决方式:

1.向下抽取state

我们可以把与text状态相关的元素,抽取出来放到Child组件中然后将state移动到该组件里:

export default function App() {
  return (
    <>
      <Child />
      <ExpensiveItem />
    </>
  );
}

function Child() {
  const [text, setText] = useState('one') 
  return (
    <p onChange={() => setText('two')}>{text}</p>
  );
}

现在如果text变化了,只有Child会重新渲染,问题解决了。

2.向上抽取组件

第一种解法可能会遇到问题:当text状态在<ExpensiveItem />的父元素中使用时就无效了。举个例子,如果我们将div的className设置为text

export default function App() {
  const [text, setText] = useState('one')
  return (
    <div className={text}>
      <p onChange={() => setText('two')}>{text}</p>
      <ExpensiveItem />
    </div>
  );
}

这种情况我们就无法将text再抽取到子组件Child中了,因为父元素也使用了text,那应该怎么做呢?我们可以用第二种方法:

export default function App() {
  return (
    <TextHandler>
      <ExpensiveItem />
    </TextHandler>
  );
}

function TextHandler({ children }) {
  const [text, setText] = useState('one')
  return (
    <div className={text}>
      <p onChange={() => setText('two')}>{text}</p>
      {children}
     </div
  )
}

我们将App中的代码改为用TextHandler组件包裹,依赖text状态的元素放入TextHandler组件里,不依赖text的部分就依然放在App组件中,然后以children属性传递给TextHandler。当text变化时,TextHandler会重新渲染,但是它仍然保存着上一次从App中拿到的相同的children属性, 因此<ExpensiveItem />组件不会重新渲染,解决问题。