react相关性能优化(持续更新

1.避免重复渲染

当一个组件的props或者state改变时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际的DOM。当他们不相等时,React会更新DOM。

1)如果一个class组件你可以你的组件可以通过重写这个生命周期函数shouldComponentUpdate来提升速度, 它是在重新渲染过程开始前触发的。 这个函数默认返回true,可使React执行更新:

1
2
3
4
5
shouldComponentUpdate(nextProps, nextState) {

return true;

}

如果你知道在某些情况下你的组件不需要更新,你可以在shouldComponentUpdate内返回false来跳过整个渲染进程,该进程包括了对该组件和之后的内容调用render()指令

当然大部分情况下可以用下面的方式创建组件:React 已经提供了一位好帮手来帮你实现这种常见的模式 - 你只要继承 React.PureComponent 就行了。所以这段代码可以改成以下这种更简洁的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class CounterButton extends React.PureComponent {

constructor(props) {

super(props);

this.state = {count: 1};

}

render() {

return (

<button

color={this.props.color}

onClick={() => this.setState(state => ({count: state.count + 1}))}>

Count: {this.state.count}

</button>

);

}

}

这个效果和shouldComponentUpdate 是一样的都是浅比较。

2)如果是函数型组件,可以通过Effect Hook ,他可以让你在函数组件中执行副作用操作,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作

有一个特性被内置到了 useEffect 的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:

1
2
3
4
5
useEffect(() => {

document.title = `You clicked ${count} times`;

}, [count]); // 仅在 count 更改时更新

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

2不可变数据的力量

这里有一个 JavaScript 的提案,旨在添加对象扩展属性以使得更新不可变对象变得更方便:

1
2
3
4
5
function updateColorMap(colormap) {

return {...colormap, right: 'blue'};

}

如果你在使用 Create React App,Object.assign 以及对象扩展运算符已经默认支持了。

3使用不可变数据结构

Immutable.js 是另一种解决方案。它通过结构共享提供了不可变、持久化集合:

  • 不可变:一旦创建,一个集合便不能再被修改。

  • 持久化:对集合进行修改,会创建一个新的集合。之前的集合仍然有效。

  • 结构共享:新的集合会尽可能复用之前集合的结构,以最小化拷贝操作来提高性能。

不可变数据使得追踪变更非常容易。每次变更都会生成一个新的对象使得我们只需要检查对象的引用是否改变。举个例子,这是一段很常见的 JavaScript 代码:

1
2
3
4
5
6
7
const x = { foo: 'bar' };

const y = x;

y.foo = 'baz';

x === y; // true

由于 y 被指向和 x 相同的对象,虽然我们修改了 y,但是对比结果还是 true。你可以使用 immutable.js 来写相似的代码:

1
2
3
4
5
6
7
8
9
10
11
const SomeRecord = Immutable.Record({ foo: null });

const x = new SomeRecord({ foo: 'bar' });

const y = x.set('foo', 'baz');

const z = x.set('foo', 'bar');

x === y; // false

x === z; // true

在这个例子中,修改 x 后我们得到了一个新的引用,我们可以通过判断引用 (x === y) 来验证 y 中存的值和原本 x 中存的值不同。

还有其他可以帮助实现不可变数据的库,分别是 Immerimmutability-helper 以及 seamless-immutable

不可变数据结构使你可以方便地追踪对象的变化,这是应用 shouldComponentUpdate 所需要的。让性能得以提升。

4.虚拟化长列表

如果你的应用渲染了长列表(上百甚至上千的数据),我们推荐使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地降低重新渲染组件消耗的时间,以及创建 DOM 节点的数量。

react-windowreact-virtualized 是热门的虚拟滚动库。 它们提供了多种可复用的组件,用于展示列表、网格和表格数据。 如果你想要一些针对你的应用做定制优化,你也可以创建你自己的虚拟滚动组件,就像 Twitter 所做的

5.key的优化

当循环渲染列表的时候,一定要给相关元素加key。 key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。

React 支持 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。以下例子在新增 key 之后使得之前的低效转换变得高效。

6. 跳到不必要的渲染,记忆计算结果

useMemo Hook 允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果:

1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

这行代码会调用 computeExpensiveValue(a, b)。但如果依赖数组 [a, b] 自上次赋值以来没有改变过,useMemo 会跳过二次调用,只是简单复用它上一次返回的值。

记住,传给 useMemo 的函数是在渲染期间运行的。不要在其中做任何你通常不会在渲染期间做的事。举个例子,副作用属于 useEffect,而不是 useMemo。

你可以把 useMemo 作为一种性能优化的手段,但不要把它当做一种语义上的保证。未来,React 可能会选择「忘掉」一些之前记住的值并在下一次渲染时重新计算它们,比如为离屏组件释放内存。建议自己编写相关代码以便没有 useMemo 也能正常工作 —— 然后把它加入性能优化。(在某些取值必须 从不 被重新计算的罕见场景,你可以 惰性初始化 一个 ref。)

方便起见,useMemo 也允许你跳过一次子节点的昂贵的重新渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Parent({ a, b }) {

// Only re-rendered if `a` changes:

const child1 = useMemo(() => <Child1 a={a} />, [a]);

// Only re-rendered if `b` changes:

const child2 = useMemo(() => <Child2 b={b} />, [b]);

return (

<>

{child1}

{child2}

</>

)

}

注意这种方式在循环中是无效的,因为 Hook 调用 不能 被放在循环中。但你可以为列表项抽取一个单独的组件,并在其中调用 useMemo。

8.不要滥用props

props尽量只传需要的数据,避免多余的更新,尽量避免使用{…props}

9.避免不必要的计算

reselect 其实就是 redux 的一个中间件,它通过计算获得新的 state,然后传递到 Redux Store。其主要就是进行了中间的那一步计算,使得计算的状态被缓存,从而根据传入的 state 判断是否需要调用计算函数,而不用在组件每次更新的时候都进行调用,从而更加高效。