The Power Of Not Mutating Data
本次文章可能有点标题党~ 原意是想表达:不可变数据的力量
也可见官方文档:
zh-hans.reactjs.org/docs/optimi…
起因
随着项目的越做越大、功能迭代、业务迭代越来越多,避免不了需要进行性能优化,于是再次打开了武功秘籍(ps:官方文档)之性能优化篇。看到很神奇的一个标题,the power of not moutating data,不可变数据的力量。一顿翻看。。。
其实总结就一句话,修改state中的数据时候,我们尽量不需要修改原有的数据源,进行一个浅拷贝再重新赋值即可,尽量遵循单向数据流。
代码总结就:
const xxx = [...this.state.xxx]
为啥开发中习惯这么操作后,官方还需要花费篇幅进行分析~
示例:
handleClick(){
const book = {id:'04',name:'react-redux',count:9}
// 在PureComponent 中直接修改state中的数据,不会执行render
this.state.books.push(book)
this.setState({books:this.state.books})
// const books = [...this.state.books]
// books.push(book)
// this.setState({books})
}
关于SCU
Component
与 PureCompoent
每个节点中,SCU
代表 shouldComponentUpdate
返回的值,而 vDOMEq
代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停。
节点 C2 的 shouldComponentUpdate
返回了 false
,React 因而不会去渲染 C2,也因此 C4 和 C5 的 shouldComponentUpdate
不会被调用到。
对于 C1 和 C3,shouldComponentUpdate
返回了 true
,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate
返回了 true
,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。
这里最主要的区别就是 shouComponentUpdate
方法。
shouldComponentUpdate(nextProps,nextState){
if(this.props.message !== nextProps.message){
return true
}
return false
}
而在函数式组件中,使用memo
高阶函数的进行包裹使用,内部其实也是帮我们完成了shallowEqual
的操作
关于setState
这里梳理几个关于React中常见的问题
为什么使用setState
?
在React中修改数据,为什么只能通过setState的方式。 this.state.xxxx 方式修改值,并不能引起界面的刷新
- 修改了state的值后,希望React根据最新的state值来渲染界面,但这种方式React并不知道界面数据发生了变化。
- React并没有提供类似 Vue2 中,Object.defineProperty或者 Vue3中的Proxy 来监听数据的变化。
- 必须通过setState的方式,来告知React数据发生变化
为什么setState设计为异步(React18之后)?
- 会显著的提升性能, 如果每调用一次setState都会去重新渲染界面,意味着render函数都会重新执行,效率低下。 最好的办法就是,获取多个更新。进行批量的处理。
- 如果是同步更新state,但是还没有执行 render函数。那么会造成 state props的错乱 state 和 props 不一致,会在生产中造成很多问题。
//react18之前,类似 setTimeout Promise DOM的原生事件中
setTimeout(()=>{
this.setState({
message:''
})
},0)
// react18之后都是 异步操作
// 也可以使用 flushSync
import {flushSync} from 'react-dom'
flushSync(()=>{
this.setState({
message:''
})
})
至于PureComponent
到底做什么事情
了解真相才能获得真正的自由~ 接下来就进行源码的解读
源码解读
ps:本次下载的react源码版本为:v-18.3.0
标记是否为PureComponent组件
ReactBaseClasses.js 文件中,在PureComponent
的原型上面挂载了一个isPureReactComponent
的标记,用于 checkShouldComponentUpdate
方法中判断 是否要进行shallowEqual(浅比较) 。
checkShouldComponentUpdate
react-reconciler 文件夹下,找到 ReactFiberClassComponent.new.js文件。我们知道,React更新流程,大致为:props/state改变 ---》render函数重新执行 ----》产生新的DOM树 ---》与旧的DOM树进行diff对比 ---》计算出差异进行更新----》更新真实的DOM。
在state中数据发生变化的时候,会重新执行render函数。产生的新的DOM树与旧的DOM树进行一个diff的比较。
其实看到该文件的时候,Fiber其实就可以理解为虚拟DOM。
检索 shallowEqual
这个方法。
浅比较
通过源码可以发现,这里仅仅通过Object.keys()
进行了一层浅比较。
1. is(objA, objB)
,这里判断对象是否是同一个,React中针对is方法进行一个单独的封装。
2. 判断obj 是否为对象,不是对象 也返回fasle
3. 对obj中的属性长度进行比较
4. 最后就是进行obj中的属性,即key值是否发生变化。
总结
1、开发过程中尽量使用PureComponent
memo
2、修改state中的数据先进行数据源的拷贝,再进行setState。
3、无论是React 还是Vue 中都尽量遵循单向数据流的设计思想。