前言
目标
目前在工作中,大量的我的项目都是应用 react 来进行发展的,理解把握下 react 的性能优化对我的项目的体验和可维护性都有很大的益处,上面介绍下在 react 中能够使用的一些性能优化形式;
性能优化思路
对于类式组件和函数式组件来看,都能够从以下几个方面去思考如何可能进行性能优化
缩小从新 render 的次数
缩小渲染的节点
升高渲染计算量
正当设计组件
缩小从新 render 的次数
在 react 里工夫耗时最多的一个中央是 reconciliation(reconciliation 的最终目标是以最无效的形式,依据新的状态来更新 UI,咱们能够简略地了解为 diff),如果不执行 render,也就不须要 reconciliation,所以能够看出缩小 render 在性能优化过程中的重要水平了。
PureComponent
React.PureComponent 与 React.Component 很类似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层比照 prop 和 state 的形式来实现了该函数。
须要留神的是在应用 PureComponent 的组件中,在 props 或者 state 的属性值是对象的状况下,并不能阻止不必要的渲染,是因为主动加载的 shouldComponentUpdate 外面做的只是浅比拟,所以想要用 PureComponent 的个性,应该恪守准则:
确保数据类型是值类型
如果是援用类型,不该当有深层次的数据变动 (解构)
ShouldComponentUpdate
能够利用此事件来决定何时须要从新渲染组件。如果组件 props 更改或调用 setState,则此函数返回一个 Boolean 值,为 true 则会从新渲染组件,反之则不会从新渲染组件。前端培训
在这两种状况下组件都会从新渲染。咱们能够在这个生命周期事件中搁置一个自定义逻辑,以决定是否调用组件的 render 函数。
上面举一个小的例子来辅助了解下:
比方要在你的利用中展现学生的详细资料,每个学生都蕴含有多个属性,如姓名、年龄、喜好、身高、体重、家庭住址、父母姓名等;在这个组件场景中,只须要展现学生的姓名、年龄、住址,其余的信息不须要在这里展现,所以在现实状况下,除去姓名、年龄、住址以外的信息变动组件是不须要从新渲染的;
示例代码如下:
import React from “react”;
export default class ShouldComponentUpdateUsage extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "小明",
age: 12,
address: "xxxxxx",
height: 165,
weight: 40
}
}
componentDidMount() {
setTimeout(() => {
this.setState({
height: 168,
weight: 45
});
}, 5000)
}
shouldComponentUpdate(nextProps, nextState) {
if(nextState.name !== this.state.name || nextState.age !== this.state.age || nextState.address !== this.state.address) {return true;}
return false;
}
render() {
const {name, age, address} = this.state;
return (
<div>
<p>Student name: {name} </p>
<p>Student age:{age} </p>
<p>Student address:{address} </p>
</div>
)
}
}
依照 React 团队的说法,shouldComponentUpdate 是保障性能的紧急进口,既然是紧急进口,那就意味着咱们轻易用不到它。但既然有这样一个紧急进口,那阐明有时候它还是很有必要的。所以咱们要搞清楚到底什么时候才须要应用这个紧急进口。
应用准则
当你感觉,被扭转的 state 或者 props,不须要更新视图时,你就应该思考要不要应用它。
须要留神的一个中央是:扭转之后,又不须要更新视图的状态,也不应该放在 state 中。
shouldComponentUpdate 的应用,也是有代价的。如果解决得不好,甚至比多 render 一次更耗费性能,另外也会使组件的复杂度增大,个别状况下应用 PureComponent 即可;
React.memo
如果你的组件在雷同 props 的状况下渲染雷同的后果,那么你能够通过将其包装在 React.memo 中调用,以此通过记忆组件渲染后果的形式来进步组件的性能体现。这意味着在这种状况下,React 将跳过渲染组件的操作并间接复用最近一次渲染的后果。
React.memo 仅查看 props 变更。如果函数组件被 React.memo 包裹,且其实现中领有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会从新渲染。
默认状况下其只会对简单对象做浅层比照,如果你想要管制比照过程,那么请将自定义的比拟函数通过第二个参数传入来实现。
function MyComponent(props) {
/ 应用 props 渲染 /
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 办法的返回后果与
将 prevProps 传入 render 办法的返回后果统一则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
留神
与 class 组件中 shouldComponentUpdate() 办法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 办法的返回值相同。
正当应用 Context
Context 提供了一个无需为每层组件手动增加 props,就能在组件树间进行数据传递的办法。正是因为其这个特点,它是能够穿透 React.memo 或者 shouldComponentUpdate 的比对的,也就是说,一旦 Context 的 Value 变动,所有依赖该 Context 的组件会全副 forceUpdate. 这个和 Mobx 和 Vue 的响应式零碎不同,Context API 并不能细粒度地检测哪些组件依赖哪些状态。
准则
Context 中只定义被大多数组件所共用的属性,例如以后用户的信息、主题或者抉择的语言。
防止应用匿名函数
首先来看下上面这段代码
const MenuContainer = ({list}) => (
<Menu>
{list.map((i) => (<MenuItem key={i.id} onClick={() => handleClick(i.id)} value={i.value} />
))}
</Menu>
);
下面这个写法看起来是比拟简洁,然而有一个潜在问题是匿名函数在每次渲染时都会有不同的援用,这样就会导致 Menu 组件会呈现反复渲染的问题;能够应用 useCallback 来进行优化:
const MenuContainer = ({list}) => {
const handleClick = useCallback(
(id) => () => {// ...},
[],
);
return (
<Menu>
{list.map((i) => (<MenuItem key={i.id} id={i.id} onClick={handleClick(i.id)} value={i.value} />
))}
</Menu>
);
};
缩小渲染的节点
组件懒加载
组件懒加载能够让 react 利用在真正须要展现这个组件的时候再去展现,能够比拟无效的缩小渲染的节点数进步页面的加载速度
React 官网在 16.6 版本后引入了新的个性:React.lazy 和 React.Suspense, 这两个组件的配合应用能够比拟不便进行组件懒加载的实现;
React.lazy
该办法次要的作用就是能够定义一个动静加载的组件,这能够间接缩减打包后 bundle 的体积,并且能够提早加载在首次渲染时不须要渲染的组件,代码示例如下:
应用之前
import SomeComponent from ‘./SomeComponent’;
应用之后
const SomeComponent = React.lazy(() => import(‘./SomeComponent’));
应用 React.lazy 的动静引入个性须要 JS 环境反对 Promise。在 IE11 及以下版本的浏览器中须要通过引入 polyfill 来应用该个性。
React.Suspense
该组件目前次要的作用就是配合渲染 lazy 组件,这样就能够在期待加载 lazy 组件时展现 loading 元素,不至于间接空白,晋升用户体验;
Suspense 组件中的 fallback 属性承受任何在组件加载过程中你想展现的 React 元素。你能够将 Suspense 组件置于懒加载组件之上的任何地位,你甚至能够用一个 Suspense 组件包裹多个懒加载组件。
代码示例如下:
import React, {Suspense} from ‘react’;
const OtherComponent = React.lazy(() => import(‘./OtherComponent’));
const AnotherComponent = React.lazy(() => import(‘./AnotherComponent’));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
有一点要特地留神的是:React.lazy 和 Suspense 技术还不反对服务端渲染。如果你想要在应用服务端渲染的利用中应用,举荐应用 Loadable Components 这个库,能够联合这个文档服务端渲染打包指南来进行查看。
另外在业内也有一些比拟成熟的 react 组件懒加载开源库:react-loadable 和 react-lazyload,感兴趣的能够联合看下;
虚构列表
虚构列表是一种依据滚动容器元素的可视区域来渲染长列表数据中某一个局部数据的技术,在开发一些我的项目中,会遇到一些不是间接分页来加载列表数据的场景,在这种状况下能够思考联合虚构列表来进行优化,能够达到依据容器元素的高度以及列表项元素的高度来显示长列表数据中的某一个局部,而不是去残缺地渲染长列表,以进步有限滚动的性能。
能够关注下放两个比拟罕用的类库来进行深刻理解
react-virtualized
react-window
升高渲染计算量
useMemo
先来看下 useMemo 的根本应用办法:
function computeExpensiveValue(a, b) {
// 计算量很大的一些逻辑
return xxx
}
const memoizedValue = useMemo(computeExpensiveValue, [a, b]);
useMemo 的第一个参数就是一个函数,这个函数返回的值会被缓存起来,同时这个值会作为 useMemo 的返回值,第二个参数是一个数组依赖,如果数组外面的值有变动,那么就会从新去执行第一个参数外面的函数,并将函数返回的值缓存起来并作为 useMemo 的返回值。
留神
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值;
计算量如果很小的计算函数,也能够抉择不应用 useMemo,因为这点优化并不会作为性能瓶颈的要点,反而可能应用谬误还会引起一些性能问题。
遍历展现视图时应用 key
key 帮忙 React 辨认哪些元素扭转了,比方被增加或删除。因而你该当给数组中的每一个元素赋予一个确定的标识。
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
应用 key 注意事项:
最好是这个元素在列表中领有的一个举世无双的字符串。通常,咱们应用数据中的 id 来作为元素的 key,当元素没有确定 id 的时候,万不得已你能够应用元素索引 index 作为 key
元素的 key 只有放在就近的数组上下文中才有意义。例如,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 元素上,而不是放在 ListItem 组件中的
元素上。
正当设计组件
简化 props
如果一个组件的 props 比较复杂的话,会影响 shallowCompare 的效率,也会使这个组件变得难以保护,另外也与“繁多职责”的准则不合乎,能够思考进行拆解。
简化 State
在设计组件的 state 时,能够依照这个准则来:须要组件响应它的变动或者须要渲染到视图中的数据,才放到 state 中;这样能够防止不必要的数据变动导致组件从新渲染。
缩小组件嵌套
个别不必要的节点嵌套都是滥用高阶组件 /RenderProps 导致的。所以还是那句话‘只有在必要时才应用 xxx’。有很多种形式来代替高阶组件 /RenderProps,例如优先应用 props、React Hooks