前言
在 React 组件中,我们通常会使用memo或useMemo来优化组件性能,本质上是为了避免组件不必要重的重复渲染,这里分享两种不同的技巧,也能够达到避免重复渲染的效果:
问题
如下,是一个严重影响渲染性能的组件:
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 />组件。我们可以直接用memo或useMemo来优化,但是现在已经有很多这方面的文章了,这里说下另外两种解决方式:
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 />组件不会重新渲染,解决问题。