作者简介 Amy 蚂蚁金服·数据体验技术团队

最近在需求开发的过程中,踩了很多因为更新引用数据但是页面不重新渲染的坑,所以对这块的内容总结了一下。

在谈及 Immutable 数据之前,我们先来聊聊 React 组件是怎么渲染更新的。

React 组件的更新方式

state 的直接改变

React 组件的更新是由状组件态改变引起,这里的状态一般指组件内的 state 对象,当某个组件的 state 发生改变时,组件在更新的时候将会经历如下过程:

  • shouldComponentUpdate
  • componentWillUpdate
  • render()
  • componentDidUpdate

state 的更新一般是通过在组件内部执行 this.setState 操作, 但是 setState 是一个异步操作,它只是执行将要修改的状态放在一个执行队列中,React 会出于性能考虑,把多个 setState 的操作合并成一次进行执行。

props 的改变

除了 state 会导致组件更新外,外部传进来的 props 也会使组件更新,但是这种是当子组件直接使用父组件的 props 来渲染, 例如:

render(){return <span>{this.props.text}</span>
}复制代码

当 props 更新时,子组件将会渲染更新,其运行顺序如下:

  • componentWillReceiveProps (nextProps)
  • static getDerivedStateFromProps()
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • getSnapshotBeforeUpdate()
  • componentDidUpdate

示例代码 根据示例中的输出显示,React 组件的生命周期的运行顺序可以一目了然了。

state 的间接改变

还有一种就是将 props 转换成 state 来渲染组件的,这时候如果 props 更新了,要使组件重新渲染,就需要在 componentWillReceiveProps 生命周期中将最新的 props 赋值给 state,例如:

class Example extends React.PureComponent {constructor(props) {super(props);this.state = {text: props.text};}componentWillReceiveProps(nextProps) {this.setState({text: nextProps.text});}render() {return <div>{this.state.text}</div>}
}
复制代码

这种情况的更新也是 setState 的一种变种形式,只是 state 的来源不同。

React 的组件更新过程

当某个 React 组件发生更新时(state 或者 props 发生改变),React 将会根据新的状态构建一棵新的 Virtual DOM 树,然后使用 diff 算法将这个 Virtual DOM 和 之前的 Virtual DOM 进行对比,如果不同则重新渲染。React 会在渲染之前会先调用 shouldComponentUpdate 这个函数是否需要重新渲染,整个链路的源码分析可参照这里,React 中 shouldComponentUpdate 函数的默认返回值是 true,所以组件中的任何一个位置发生改变了,组件中其他不变的部分也会重新渲染。

当一个组件渲染的机构很简单的时候,这种因为某个状态改变引起整个组件改变的影响可能不大,但是当组件渲染很复杂的时候,比如一个很多节点的树形组件,当更改某一个叶子节点的状态时,整个树形都会重新渲染,即使是那些状态没有更新的节点,这在某种程度上耗费了性能,导致整个组件的渲染和更新速度变慢,从而影响用户体验。

PureComponent 的浅比较

基于上面提到的性能问题,所以 React 又推出了 PureComponent, 和它有类似功能的是 PureRenderMixin 插件,PureRenderMixin 插件实现了 shouldComponentUpdate 方法, 该方法主要是执行了一次浅比较,代码如下:

function shallowCompare(instance, nextProps, nextState) {return (!shallowEqual(instance.props, nextProps) ||!shallowEqual(instance.state, nextState));
}
复制代码

PureComponent 判断是否需要更新的逻辑和 PureRenderMixin 插件一样,源码如下:

 if (this._compositeType === CompositeTypes.PureClass) {shouldUpdate =!shallowEqual(prevProps, nextProps) ||!shallowEqual(inst.state, nextState);}复制代码

利用上述两种方法虽然可以避免没有改变的元素发生不必要的重新渲染,但是使用上面的这种浅比较还是会带来一些问题:

假如传给某个组件的 props 的数据结构如下所示:

const data = {list: [{name: 'aaa',sex: 'man'},{name: 'bbb',sex: 'woman'}],status: true,
}复制代码

由于上述的 data 数据是一个引用类型,当更改了其中的某一字段,并期望在改变之后组件可以重新渲染的时候,发现使用 PureComponent 的时候,发现组件并没有重新渲染,因为更改后的数据和修改前的数据使用的同一个内存,所有比较的结果永远都是 false, 导致组件并没有重新渲染。

解决问题的几种方式

要解决上面这个问题,就要考虑怎么实现更新后的引用数据和原数据指向的内存不一致,也就是使用Immutable数据,下面列举自己总结的几种方法;

使用 lodash 的深拷贝

这种方式的实现代码如下:

import _ from "lodash";const data = {list: [{name: 'aaa',sex: 'man'}, {name: 'bbb',sex: 'woman'}],status: true,}const newData = _.cloneDeepWith(data);shallowEqual(data, newData) //false//更改其中的某个字段再比较newData.list[0].name = 'ccc';shallowEqual(data.list, newData.list)  //false复制代码

这种方式就是先深拷贝复杂类型,然后更改其中的某项值,这样两者使用的是不同的引用地址,自然在比较的时候返回的就是 false,但是有一个缺点是这种深拷贝的实现会耗费很多内存。

使用 JSON.stringify()

这种方式相当于一种黑魔法了,使用方式如下:

const data = {list: [{name: 'aaa',sex: 'man'}, {name: 'bbb',sex: 'woman'}],status: true,c: function(){console.log('aaa')}}const newData = JSON.parse(JSON.stringify(data))shallowEqual(data, newData) //false//更改其中的某个字段再比较newData.list[0].name = 'ccc';shallowEqual(data.list, newData.list)  //false
复制代码

这种方式其实就是深拷贝的一种变种形式,它的缺点除了和上面那种一样之外,还有两点就是如果你的对象里有函数,函数无法被拷贝下来,同时也无法拷贝 copyObj 对象原型链上的属性和方法

使用 Object 解构

Object 解构是 ES6 语法,先上一段代码分析一下:

  const data = {list: [{name: 'aaa',sex: 'man'}, {name: 'bbb',sex: 'woman'}],status: true,}const newData =  {...data};console.log(shallowEqual(data, newData));  //falseconsole.log(shallowEqual(data, newData));  //true//添加一个字段newData.status = false;console.log(shallowEqual(data, newData));  //false//修改复杂类型的某个字段newData.list[0].name = 'abbb';console.log(shallowEqual(data, newData));  //true
复制代码

通过上面的测试可以发现: 当修改数据中的简单类型的变量的时候,使用解构是可以解决问题的,但是当修改其中的复杂类型的时候就不能检测到(曾经踩过一个大坑)。

因为解构在经过 babel 编译后是 Object.assign(), 但是它是一个浅拷贝,用图来表示如下:

这种方式的缺点显而易见了,对于复杂类型的数据无法检测到其更新。

使用第三方库

业界提供了一些库来解决这个问题,比如 immutability-helper , immutable 或者immutability-helper-x。

immutability-helper

一个基于 Array 和 Object 操作的库,就一个文件但是使用起来很方便。例如上面的例子就可以写成下面这种:

   import update from 'immutability-helper';const data = {list: [{name: 'aaa',sex: 'man'}, {name: 'bbb',sex: 'woman'}],status: true,}const newData = update(data, { list: { 0: { name: { $set: "bbb" } } } });console.log(this.shallowEqual(data, newData));  //false//当只发生如下改变时const newData = update(data,{status:{$set: false}});console.log(this.shallowEqual(data, newData));  //falseconsole.log(this.shallowEqual(data.list, newData.list));  //true
复制代码

同时可以发现当只改变 data 中的 status 字段时,比较前后两者的引用字段,发现是共享内存的,这在一定程度上节省了内存的消耗。而且 API 都是熟知的一些对 Array 和 Object 操作,比较容易上手。

immutable

相比于 immutability-helper, immutable 则要强大许多,但是与此同时,也增加了学习的成本,因为需要学习新的 API,由于没怎么用过,在此不再赘述,具体知识点可移步这里。

immutability-helper-x

最后推荐下另一个开源库immutability-helper-x,API更好用哦~可以将

const newData = update(data, { list: { 0: { name: { $set: "bbb" } } } });
复制代码

简化为可读性更强的

const newData = update.$set(data, 'list.0.name', "bbb");
或者
const newData = update.$set(data, ['list', '0', 'name'], "bbb");
复制代码

写在最后

在 React 项目中,还是最好使用 immutable 数据,这样可以避免很多浑然不知的 bug。 以上只是个人在实际开发中的一些总结和积累,如有阐述得不对的地方欢迎拍砖~

对我们团队感兴趣的可以关注专栏,关注github或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

Immutable 操作在 React 中的实践相关推荐

  1. antd中table组件中如何进行换行操作(react中)

    antd中table组件中如何进行换行操作(react中) 说明 效果 数据 代码 说明 react项目,遇到某字段对应的单元格内需要换行. 最初想偷懒,尝试了在数据中加入回车.使用模板字符串.转义字 ...

  2. Immutable 详解及 React 中实践

    Shared mutable state is the root of all evil(共享的可变状态是万恶之源) -- Pete Hunt 有人说 Immutable 可以给 React 应用带来 ...

  3. Immutable 详解及 React 中实践 1

    本文转自:https://github.com/camsong/blog/issues/3 Shared mutable state is the root of all evil(共享的可变状态是万 ...

  4. 大道至简——React Native在直播应用中的实践

    声明:本文来自「七牛云主办的架构师实践日--亿级移动应用架构最佳实践」的演讲内容整理.PPT.速记和现场演讲视频等参见"七牛架构师实践日"官网. 嘉宾:卜赫,七牛云布道师. 责编: ...

  5. React中Immutable的使用

    文章目录 1. 介绍 2. 优缺点 3. 对象处理 4. 数组处理 5. 优化组件渲染 6. immutable和redux集合使用 1. 介绍 假设当前 redux 的大小为 1G,现在要修改 re ...

  6. [react] React中怎么操作虚拟DOM的Class属性

    [react] React中怎么操作虚拟DOM的Class属性 render() { this.debug('render ....'); this.components.push(<Atten ...

  7. 管理系统中计算机应用实践大纲,管理系统中计算机应用实践技能考核大纲及操作指导...

    <管理系统中计算机应用实践技能考核大纲及操作指导>由会员分享,可在线阅读,更多相关<管理系统中计算机应用实践技能考核大纲及操作指导(15页珍藏版)>请在人人文库网上搜索. 1. ...

  8. 案例:React Native在字节跳动游戏营销场景中的实践

    ????????关注后回复 "进群" ,拉你进程序员交流群???????? 作者丨熊文源 来源丨前端之巅(ID:frontshow) https://mp.weixin.qq.co ...

  9. React飞行日记(七) - 在React中使用DOM操作

    ×在React中使用DOM操作 React中很少直接用到DOM操作, 因为所有的功能都可以基于数据驱动来完成, DOM是框架帮我们做的事情, 但是也有些喜欢用DOM操作的开发者, 那么看看如何在Rea ...

最新文章

  1. 知名公司薪水(转帖)2007
  2. 【STM32】 keil软件工具--configuration详解(下)
  3. 【ArcGIS遇上Python】ArcGIS Python实现批量化矢量和栅格数据重命名
  4. 2020美国纽约大学计算机科学排名,2020美国纽约大学排名第几
  5. VMware桥接模式无法连网
  6. 小程序--计算正负数个数
  7. SQL 2008 R2 收缩日志,不用修改简单模式
  8. SlidingMenu 源码分析
  9. 回归任务中的评价指标MAE,MSE,RMSE,R-Squared
  10. puppet 安装详解
  11. 开源中文语音识别项目介绍:ASRFrame
  12. 翁凯C语言程序设计期末考试
  13. 服务器鼠标键盘进系统不能用,笔记本开机后鼠标键盘都不能用了怎么办?
  14. oracle字段名小写,oracle表名、字段名大小写问题。
  15. linux中deb文件怎么安装,deb是什么文件?deb文件怎么安装?
  16. 快速部署支持 Makedown 和 LaTeX 等格式的 Zbox-Wiki 文档共享站点
  17. Android 系统应用开发实战
  18. 手机怎么把照片转JPG格式?这三种手机小技巧需要知道
  19. Android中WebView控件支持地理位置定位
  20. 四川大学计算机学院软件工程期末,2015四川大学软件工程期末复习.doc

热门文章

  1. 东风小康为什么是dfsk_重庆造乘用车首次乘坐专列出口欧洲 100辆东风风光ix5抵达德国...
  2. JAVA集合Set之HashSet详解_Java基础———集合之HashSet详解
  3. pt-slot.php,Pwn In Kernel(一):基础知识
  4. html5 audio 资源,HTML5 Audio(音频)
  5. java生成点阵图_Android从SD卡读取图片并显示为点阵图
  6. delphi formshow 刷新_OPPO K7x部分配置和外观公布90Hz刷新率11·4发布
  7. mysql padding_解决RGB模式下图片的padding(补边框)问题(含代码实现)
  8. python里数字怎么表示_Python 中的数字—Python 学习笔记
  9. 模拟文件管理器的java可以编译但无法运行_在java学习经典问题he解答(6)
  10. es6 提取数组对象一部分_ES6新特性你了解了多少呢?