前言
传统的Diff算法O(N3),React Diff基于三大前提将复杂度降为O(N)
1.tree diff,跨层dom操作比较少,结构不同直接销毁
2.component diff,相同类型节点有相同树形结构,只做更新,不同类型直接销毁
3.element diff,同层子元素根据唯一标识做区分
一.代码优化
1.key
设置唯一稳定的key
render() {
return (
<div>
this.state.user.map(item => <div key={item.id}>{item.name}</div>)
</div>
)
}
2.节点内容
节点类型分两大类,一类是DOM元素类型(如div,span),另一类是React组件
Dom元素比较属性和内容
<div style={{color: 'red'}} onClick={() =>this.handleClick()}>变化前</div>
...
// 变化后属性和内容都变化了
<div style={{color: 'red'}} onClick={() =>this.handleClick()}>变化后</div>
注意引用类型的比较
{color: 'red'} === {color: 'red'} // false
var fun1 = () => {}
var fun2 = () => {}
fun1 === fun2 // false
优化后,属性指向都不变
const style = {color: 'red'}
...
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
...
render() {
return {
<div style={style} onClick={this.handleClick}>更改后</div>
}
}
redux版本优化前,
<TodoItem
key={item.id}
onRemove={() => onRemoveTodo(item.id)}
...
...
redux版本优化后
// 方法1
<TodoItem
key={item.id}
onRemove={onRemoveTodo}
id={item.id}
...
>
...
const mapDispatchToProps = (dispatch,ownProps) => ({
onRemoveTodo: ownProps.onRemoveTodo(ownProps.id)),
})
// 方法2
<TodoItem
key={item.id}
id={item.id}
...
>
...
const mapDispatchToProps = (dispatch,ownProps) => ({
onRemoveTodo: () => dispatch(onRemoveTodo(ownProps.id)),
})
3.shouldComponentUpdate(nextProps,nextState)
为了避免浪费多余的渲染,return false可以阻止组件的更新
shouldComponentUpdate(nextProps,nextState) {
return nextProps.isChange !== this.props.isChange // true or false
}
PureComponent类内部也是用了shouldComponentUpdate
// PureComponent内部
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextStat e);
}
return shouldUpdate;
shallowEqual(浅比较),只做简单类型的判断。
state = {
arr = [1,2,3,4,5]
}
shouldComponentUpdate(nextProps,nextState) {
return nextState.arr !== this.state.nextState // false
}
handleClick = () => {
const { arr } = this.state
arr.push(6)
this.setState({
arr,
})
}
render () {
...
}
解决方法deepEqual,但如果变量嵌套深会对性能有消耗
二.工具使用
1.immutable(一种不可更改的数据)
网上一般写法
import { is } from 'immutable'
...
shouldComponentUpdate = (nextProps = {} , nextState = {}) => {
if(Object.keys(this.props).length !== Object.keys(nextProps).length ||
Object.keys(this.state).length !== Object.keys(nextState).length
) {
return true
}
for(const key in nextProps) {
if(this.props[key] !== nextProps[key] || !is(this.props[key],nextProps[key])) {
return true
}
}
for(const key in nextState) {
if(this.state[key] !== nextState[key] || !is(this.state[key],nextState[key])) {
return true
}
}
return false
}
...
我项目中优化前后对比
shouldComponentUpdate = (nextProps = {} , nextState = {}) => {
if(!is(nextState,this.state)) {
return true
}
return false
}
使用了结构共享,避免deepCopy,可节省内存
2.reselect
一种选择器中间件,认为输入参数state相同,就没必要进行计算,直接抽取以往的值
const mapState = (state)=>({
todos:state.todos,
filter:state.filter,
visibleTodos:getVisibleTodos(state.todos,state.filter)
});
//selector.js
export const todosSelector = (state) => state.todos;
export const filterSelector = (state) => state.filter;
export const visibleTodosSelector = createSelector(
[todosSelector,filterSelector],
(todos,filter)=>{return getVisibleTodos(todos,filter)} //这里假设已经定义一个getVisibleTodos函数用来返回要显示哪些todo项
);
//container.js
import {visibleTodosSelector}
const mapState = (state)=>({
todos:visibleTodosSelector(state)
});
3.其他
少用{...props}
适当拆分组件
压缩,合并,commonChunksPulgin(webpack4里面移除了commonChunksPulgin插件,放在了config.optimization里面)
css预处理语言写法,防止多余编译
三.Fiber架构迁移
以往的react渲染是一气呵成,不能打断,同步渲染计算大的话容易阻塞UI线程。
react16后提出Fiber,使react从Stack reconciler转变为fiber reconciler
将渲染分割成多个事务,使得以往的栈结构可以定制优先级,暂停,复用,其中第一个阶段是可以随时被打断的阶段,这使得某部分旧的生命周期函数造成不安全的危害
componentWillMount
componentWillReceiveProps
componentWillUpdate
用其它生命周期函数再结合两个新的生命周期函数,足以替代它们的业务场景
getDerivedStateFromProps(nextProps, prevState)
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.selectCodeId !== prevState.codeId) {
return {
codeId: nextProps.selectCodeId,
first: false,
};
}
return null;
}
getDerivedStateFromProps没有附加渲染的情况下更新状态的唯一方法,可以用来替代componentWillReceiveProps
getSnapshotBeforeUpdate(prevProps, prevState)
export default class ScrollingList extends Component {
constructor(props) {
super(props)
this.scrollRef = null
}
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
return (
this.scrollRef.scrollHeight - this.scrollRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
this.scrollRef.scrollTop =
this.scrollRef.scrollHeight - snapshot;
}
}
render() {
return (
....
);
}
}