不可变的力量

434 阅读4分钟

The Power Of Not Mutating Data

本次文章可能有点标题党~ 原意是想表达:不可变数据的力量

image.png 也可见官方文档:

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

ComponentPureCompoent

每个节点中,SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停。

image.png

节点 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(浅比较) 。

image.png

checkShouldComponentUpdate

react-reconciler 文件夹下,找到 ReactFiberClassComponent.new.js文件。我们知道,React更新流程,大致为:props/state改变 ---》render函数重新执行 ----》产生新的DOM树 ---》与旧的DOM树进行diff对比 ---》计算出差异进行更新----》更新真实的DOM。 在state中数据发生变化的时候,会重新执行render函数。产生的新的DOM树与旧的DOM树进行一个diff的比较。 其实看到该文件的时候,Fiber其实就可以理解为虚拟DOM。 检索 shallowEqual 这个方法。

image.png

浅比较

通过源码可以发现,这里仅仅通过Object.keys()进行了一层浅比较。
1. is(objA, objB) ,这里判断对象是否是同一个,React中针对is方法进行一个单独的封装。
2. 判断obj 是否为对象,不是对象 也返回fasle
3. 对obj中的属性长度进行比较
4. 最后就是进行obj中的属性,即key值是否发生变化。

image.png

总结

1、开发过程中尽量使用PureComponent memo
2、修改state中的数据先进行数据源的拷贝,再进行setState。
3、无论是React 还是Vue 中都尽量遵循单向数据流的设计思想。