熟记 Diffing 规则
1.当节点(包括 DOM 节点和组件节点)的类型发生变化时,React 会卸载该节点(包含子节点)的 DOM 树,然后重新渲染新的 DOM 树。
key
更新时 React 需要对比更新前后的变化,生成一个 mutation。没有 key,不能复用之前的组件,影响性能。
用数组索引来作为 key,由于是基于索引来决定组件的更新和复用,顺序变化时会导致非受控组件变化达不到预期,比如期望第一个元素和第二个元素对调位置,由于数组索引作为 key,非受控组件是复用的(后面有个性能提升技巧就是使用非受控组件),React 识别不出来位置变化,这里有个例子演示了索引作为 key 导致的问题。
最佳实践是在列表层使用唯一值作为 key,例如 id。
合理地拆分组件
我们知道 React 提供了组件式开发方式。写好 React 代码的基础就是组件拆分,合理地组件拆分既可以提升的代码可维护性和可复用性,结合后面的性能技巧,还可以提升性能。
1.组件拆分一个重要的原则是高内聚低耦合,子组件应该是一个独立的单元,子组件与父亲组件间的通信信息要少,数据请求应该放到子组件中,除非父组件也要使用。
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 => {
// ...
})
});
// 合并setState
this.setState(state => {
pagination: {
...state.pagination,
current: 1,
},
loading: true,
}, () => {
getData().then(res => {
// ...
})
});
// 合并前
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;
使用非受控组件
我们先看下什么是受控组件,就是子组件的状态由依赖父组件,这样会导致需要更新子组件时,必须先改变父组件的状态,也就是触发父组件的 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)
总结
关注拍乐云 Pano 的公众号,我们将为大家分享更多的技术探索和实践经验,陪你一起成长充电。


