为什么要叫一个简单的技巧来优化 react 的重渲染
因为在这个优化中并没有使用到 useMemo 或 useCallback 之类的方法,只是用了一点小技巧而已
举一个例子:
一个会耗费性能的组件
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
是的,这并不是什么新鲜玩意儿,但是也得多多注意他们,毕竟“高端的食材往往只需要采用最朴素的烹饪方式”
若有错误,欢迎评论指出
参考链接: