react介绍
React 是由 Facebook 团队主导开发的UI框架,作为当前三大前端框架(另外两个是 Angular、Vue )之一,提供了数据驱动的 UI 开发模式,以及组件化的开发方式,可以构建高性能的前端应用。
Diffing 规则
- 当节点(包括 DOM 节点和组件节点)的类型发生变化时,React 会卸载该节点(包含子节点)的 DOM 树,然后重新渲染新的 DOM 树。
- 当 DOM 节点的类型不变时,React 会保留 DOM 节点,然后对比属性变化更新 DOM 节点
- 当组件节点的类型不变时,组件实例会保持不变,对比 props 属性然后更新组件。
- 上面过程从根节点开始,递归到所有的叶子节点结束
从上面的规则我们可以看出,组件是主角,后面的性能提升点也是围绕组件的。从上面的规则可以看出大变动应该使用不同的组件, 小变动应该修改属性。记住了 Diffing 规则其实就可以写出性能不错的 React 代码。上面只是一个总结,不需要可以详细看看这遍文档协调。
key
更新时 React 需要对比更新前后的变化,生成一个 mutation。没有 key,不能复用之前的组件,影响性能。
用数组索引来作为 key,由于是基于索引来决定组件的更新和复用,顺序变化时会导致非受控组件变化达不到预期,比如期望第一个元素和第二个元素对调位置,由于数组索引作为 key,非受控组件是复用的(后面有个性能提升技巧就是使用非受控组件),React 识别不出来位置变化,这里有个例子演示了索引作为 key 导致的问题。
最佳实践是在列表层使用唯一值作为 key,例如 id。
合理地拆分组件
我们知道 React 提供了组件式开发方式。写好 React 代码的基础就是组件拆分,合理地组件拆分既可以提升的代码可维护性和可复用性,结合后面的性能技巧,还可以提升性能。
-
组件拆分一个重要的原则是高内聚低耦合,子组件应该是一个独立的单元,子组件与父亲组件间的通信信息要少,数据请求应该放到子组件中,除非父组件也要使用。
-
会复用的单元一定要抽成组件,复用组件应该不依赖外部,因此只通过 props 进行通信,如果要使用到 context 可以用一个高阶组件进行包裹
function Page(props) => { return <Card> <MeetingInfo id={props.match.params.id} /> <UserList /> </Card>;} function MeetingInfo(props) { const { info, setInfo } = useState({}); useEffect(() => { getMeeting(props.id).then(res => { setInfo(res); }); }); return <> { // 这里显示meetingInfo } </>; }
-
组件粒度也不应过细,会增加组件层级深度,也会增加维护的工作量
-
为了统一样式,应该优先考虑抽取公用的类名,而不是组件
合并setState
合并 setState 可以减少 render 执行次数,是一种很直接、也很明显的性能提升方式。
同一代码块中不要多次调用 setState
// 调用了多次setState
this.setState(
state => {
pagination: { ...state.pagination, current: 1, }},
() => { this.setState({ loading: true, });
getData().then(res => { // ... })});
// 合并setStatethis.setState(
state => {
pagination: { ...state.pagination, current: 1, },
loading: true,},
() => {
getData().then(res => { // ... })
});
同时发出的请求,尤其当这些请求耗时差不多时,可以使用 Promise.all 合并,从而减少 setState 次数
// 合并前
getMeeting().then(info => this.setState({ info }));
getUserList().then(userList => this.setState({ info }));// 合并后Promise.all([getMeeting(), getUserList(),]).then(([info, userList]) => { this.setState({ info, userList, });});
避免在 render 中做复杂的计算
理想的情况,render 只做数据的展示,实际开发中或多或少会有一些计算,但是要避免在 render 中做复杂的计算,而应该把值计算好存储在 state 或者 props 中。
// 再render中做了构建树的计算
function DeptTree(props) {return <Tree data={buildTree(props.list)} />}// 应该把树结构数据计算好存储在state中function DeptTree(props) {
const { treeData, setTreeData } = useState();
useEffect(() => { setTreeData(buildTree(props.list)); }, [ props.list ]);
return <Tree data={treeData} />
}
使用 PureComponent
PureComponent 更新前会浅比较 props 和 state 是否发生变化,如果没有发生变化时则不调用 render ,从而达到提升性能的目的。
import { useState } from 'react';
import Child from './Child';
function Component() {
const [ num, setNum ] = useState(1);
return <div>
<div>
<button onClick={() => setNum(num + 1)}>添加({num})</button>
</div>
<Child />
</div>;
}
export default Component;
// child.js
import React from 'react';
class Child extends React.PureComponent {
render() {
console.log('render')
return <div>子组件</div>;
}
}
export default Child;
因为使用了 PureComponent ,每次 num 增加,子组件的 render 并不会调用。PureComponent 做的是浅比较,因此要使用不可变数据。
使用非受控组件
我们先看下什么是受控组件,就是子组件的状态由依赖父组件,这样会导致需要更新子组件时,必须先改变父组件的状态,也就是触发父组件的 render,针对这种情况应该考虑是否可以设计为非受控组件,其实形成父子组件,大部分情况肯定是要通信的,为了避免形成受控关系,可以使用 context(通常使用 Redux ,简化了 context 的使用)进行通信,常见的弹框,控制弹框显示的值父组件是不需要使用的,通过把这个放在 context 中,可以在弹框的打开和关闭不触发父组件的 render ,可以很明显的提升性能,以下是示意代码。
// addSuccess是一个固定不变的函数,因此这个组件是个非受控组件
<Add onSuccess={this.addSuccess} />
function Add(props) {
return <Modal visible={ props.addVisible }>
{ // ... }
</Modal>;
}
export default connect(state => ({ addVisible: state[namespace].addVisible,}))(Add)
总结
以上总结的性能提升技巧,合并 setState、使用 PureComponent、使用非受控组件都是通过减少 render 次数来达到性能优化的,也是主要的性能方向。当然合并 setState 为了性能优化牺牲了代码的可读性,大型项目建议采用,小型项目可以权衡取舍。最后一个使用非受控组件是为了降低父子组件之间的耦合(也就是合理地拆分组件)而总结出来的,不但提升了代码的可维护性还提升了性能。希望阅读完这篇文章能对你写出高性能的 React 代码有帮助。
*陆荣涛前端学习交流Q群858752519
加群备注:CSDN推荐