immutable.js

  • 官方文档
  • 每次修改一个 Immutable 对象时都会创建一个新的不可变的对象,在新对象上操作并不会影响到原对象的数据。
  • immutable是一种持久化数据结构,immutable数据就是一旦创建,就不能更改的数据,每当对immutable对象进行修改的时候,就会返回一个新的immutable对象,以此来保证数据的不可变。

讲讲深浅拷贝

浅拷贝

  • 只拷贝对象的一层数据,再深处层次的引用类型value将只会拷贝引用 实现方式:
  1. Object.assign() ES6的拓展运算符
/* (1). Object.assign()或者拓展运算符  */
let a = {// 创建一个a对象
age:10,// 第一层数据
usage:{// 第二层数据
sing:'唱歌'
}
}
// 使用b对象复制a对象
let b = Object.assign({}, a) // 或者let b = {...a}
b.age = 11// 修改b对象的值(第一层,成功拷贝修改)
b.usage.sing='跳舞'//修改b对象的值(第二层,无法拷贝修改,一改同改)
console.log(a,b);
/*
结果:{ age: 10, usage: { sing: '跳舞' } } { age: 11, usage: { sing: '跳舞' } }
由此可见,使用Object.assign或者... 只能拷贝对象的第一层数据,第二层数据无法拷贝
*/

深拷贝

  1. 使用JSON.stringify()JSON.parse()实现深拷贝
/* 2. 深拷贝 */
/* (2). JSON.stringify()和JSON.parse()实现深拷贝 */
let a = {// 创建一个a对象
age:10,// 第一层数据
usage:{// 第二层数据
sing:'唱',
jump:()=>{console.log('跳');},//函数
ball:undefined// undefined
},
}

let b = JSON.parse(JSON.stringify(a))
b.usage.sing='说唱!'//修改第二层数据
console.log('a:',a,'b:',b);

/*
结果:
a: {
age: 10,
usage: { sing: '唱', jump: [Function: jump], ball: undefined }
}

b: { age: 10, usage: { sing: '说唱!' } }

由此可见,虽说成功深复制到第二层数据,但是使用这种方法进行深拷贝也是有弊端的:
它会忽略value为function(函数), undefind(未定义), symbol,的属性!
*/
  1. 使用递归实现深拷贝
const deepCopy = (obj) => {
// 优化 把值类型复制方放到这里可以少一次deepCopy调用
// if(!obj || typeof obj !== 'object') throw new Error("请传入非空对象")
if(!obj || typeof obj !== 'object') return obj
let result = {}
if (Object.prototype.toString.call(obj).indexOf('Array') > 0) {
result = []
}
// 另一种循环方式
// for (let key in obj) {
// if (obj.hasOwnProperty(key)) {
// result[key] = deepClone(obj[key])
// }
// }
Object.keys(obj).forEach(key => {
// 优化 把值类型复制方放到这里可以少一次deepCopy调用
// if (obj[key] && typeof obj[key] === 'object') {
// result[key] = deepCopy(obj[key])
// }else{
// result[key] = obj[key]
// }
result[key] = deepCopy(obj[key])
});
return result
}

let aa = {
a: undefined,
func: function(){console.log(1)},
b:2,
c: {x: 'xxx', xx: undefined},
d: null,
e: BigInt(100),
f: Symbol('s')
}
let bb = deepCopy(aa)
aa.c.x = 123
aa.func = {}
console.log("aa", aa)
console.log("bb", bb)
// aa {
// a: undefined,
// func: {},
// b: 2,
// c: { x: 123, xx: undefined },
// d: null,
// e: 100n,
// f: Symbol(s)
// }

// bb {
// a: undefined,
// func: [Function: func],
// b: 2,
// c: { x: 'xxx', xx: undefined },
// d: null,
// e: 100n,
// f: Symbol(s)
// }
  • 所谓递归就是使用循环一直读取深层数据并复制,这种方法在实际生产中99.99%不会用到,因为这样性能太差了且占用内存,一般使用第三方库来实现深复制例如immutable.js等….

为什么在React需要使用深复制

  • 我们学过Vue知道,我们根本就不需要知道什么不可变对象,也不需要考虑数据修改时必须不能影响原状态, 因为Vue的原理与React不一样,Vue要求必须修改原状态,数据才能被监听到从而重新渲染页面,但是在React中,我们必须保留原状态可用的情况下去操作新状态,由此可见在React中,一旦你在操作状态时,复制的层级不够, 那么老状态就很可能会被影响,这样来说,整个项目就很可能会出现问题了!

1. Immutable优化性能的方式

  • Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点, 其它节点则进行共享。如下图所示

image

基本使用

  1. 安装
npm install immutable
  1. 介绍Map的基本实用
import {Map} from 'immutable'//引入immutable中的Map(不是es6中的map)

// 创建一个对象
var obj = {
name:'lam',
age:10
}
var oldImmuObj = Map(obj)//将对象转化为Map
// 1.修改Map中的数据(第一个数据为键名,第二个为修改键值)
var newImmuObj = oldImmuObj.set("name","xiaoming")
console.log('旧对象:',oldImmuObj,'\n','新对象',newImmuObj)
// 2.读取属性数据(.get)
console.log('读取Map中的数据',oldImmuObj.get("name"),newImmuObj.get("name"));
// 3. 删除对应属性(.delete)
var obj2 = oldImmuObj.delete('age')
console.log('删除对应属性',obj2);
// 4. 将Map数据转化回对象(.toJS)
console.log(obj2.toJS());
// 5. Map合并(.merge)
var obj3 = Map({age:20})
console.log(obj2.merge(obj3));
// 6. 深层次对象合并(.mergeDeep)
var obj4 = Map({age:10,usage:()=>{console.log('唱跳rap篮球!');},friends:{num:10}})
console.log(obj2.mergeDeep(obj4));
// 7. 原生js转immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)
immutable.fromJS({name:'lam', age:18}) //将原生object --> Map
/* toJS和fromJS特别耗费性能 慎用! */
  1. 介绍List的基本使用(作用于数组)
import {List} from 'immutable'//引入immutable中的List
var arr = List([1,2,3])// 创建一个数组

/* 可以使用数组的方法(push,concat,pop,slice等...) */
var arr2 = arr.push(4) //不会影响老的对象结构
var arr3 = arr2.concat([5,6,7])
console.log(arr.toJS(),arr2.toJS(),arr3.toJS())

// map方法遍历(直接使用map遍历即可)
arr3.map(item=>{
console.log(item);
})

//原生js转immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)
immutable.fromJS([1,2,3,4,5]) //将原生array --> List
/* toJS和fromJS特别耗费性能 慎用! */

结果展示:

  • Map的展示:

image

  • List的展示

image

案例展示:

  • 使用immutable中的Map结合组件状态(state)实现数据的展示,深层数据再次使用Map包裹,并且将数据传递给子组件,子组件通过校验数据来判断是否重新渲染!
/* 组件代码 */
import React, { Component } from 'react'
import {Map} from 'immutable'// 引入Map

/* 父组件(类组件) */
export default class App extends Component {
// 创建组件状态
state = {
info:Map({
name:"lam",
select:'aa',
filter:Map({// 深层次的数据还需要使用Map包裹(对象里面包含对象)
text:"",
up:true,
down:false
})
})
}
// [生命周期]:组件一挂载,读取状态(Map)中的数据
componentDidMount() {
// 深层次的数据(对象中的对象)还是Map
console.log(this.state.info.get("filter"))
}

render() {
return (
<div>
{/* 设置点击修改状态 */}
<button onClick={()=>{
this.setState({
// Map方法支持链式操作修改数据(每一次状态更新数据都是新的)
info:this.state.info.set("name","肥林").set("select","dwadwa")
})
}}>点击</button>
{this.state.info.get("name")}--{this.state.info.get("select")}
{/* 组件间的通信 */}
<Child filter={this.state.info.get("filter")}/>
</div>
)
}
}

/* 子组件(类组件) */
class Child extends Component{
// 判断状态是否需要更新(判断filter的数据有没有发生变化)
shouldComponentUpdate(nextProps, nextState) {
if(this.props.filter === nextProps.filter){
return false
}

return true
}

render(){
return <div>
child
</div>
}

// [生命周期]
componentDidUpdate(){
// 校验组件是否重新渲染
console.log("组件重新渲染了")
}
}

结果展示:

image

我们可以看到,控制台中并没有输出组件重新渲染这句或,说明子组件并没有重新渲染!