什么是 Immutable Data

187 阅读4分钟

2022-07-08

什么是 Immutable Data

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

好处:提高数据处理的性能

坏处:加大数据的难度,UI库对此对象的支持不友好

目前流行的 Immutable 库有两个:

immutable.js

Facebook 工程师 Lee Byron 花费 3 年时间打造,与 React 同期出现,但没有被默认放到 React 工具集里(React 提供了简化的 Helper)。它内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也尽量与 Object 或 Array 类似。

其中有 3 种最重要的数据结构说明一下:(Java 程序员应该最熟悉了)

  • Map:键值对集合,对应于 Object,ES6 也有专门的 Map 对象
  • List:有序可重复的列表,对应于 Array
  • Set:无序且不可重复的列表

seamless-immutable

与 Immutable.js 学院派的风格不同,seamless-immutable 并没有实现完整的 Persistent Data Structure,而是使用 Object.defineProperty(因此只能在 IE9 及以上使用)扩展了 JavaScript 的 Array 和 Object 对象来实现,只支持 Array 和 Object 两种数据类型,API 基于与 Array 和 Object 操持不变。代码库非常小,压缩后下载只有 2K。而 Immutable.js 压缩后下载有 16K。

下面上代码来感受一下两者的不同:

// 原来的写法
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b);  // 打印 2
console.log(foo === bar);  //  打印 true

// 使用 immutable.js 后
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2);   // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b']));  // 使用 getIn 取值,打印 1
console.log(foo === bar);  //  打印 false

// 使用  seamless-immutable.js 后
import SImmutable from 'seamless-immutable';
foo = SImmutable({a: {b: 1}})
bar = foo.merge({a: { b: 2}})   // 使用 merge 赋值
console.log(foo.a.b);  // 像原生 Object 一样取值,打印 1
console.log(foo === bar);  //  打印 false

Immutable不是react自带的,需要安装第三方库

npm i -S immutable

Immutable提供的重要对象

List 类似于原生JS中的数组

Map类似于原生JS中的对象 Map转换只会转换第一层 获取数据的方法get不建议使用,建议使用fromJS

fromJS用于转换把原生JS对象转为对应的immutable对象

 let obj={name:'zhangsan',age:20,info:{m:1}}
 let state=Map(obj)
console.log(state)
console.log(obj)

获取immutable对象中的数据方法get/getIn

Map
console.log(state.get('name'))
console.log(state.getIn(['name']))
深层调用建议使用 getIn
console.log(state.get('info').get('m'))
console.log(state.getIn(['info','m']))

List
let state = fromJS([1,2,{id:1,name:'lisi'}])
console.log(state.get(1))
console.log(state.getIn([2,'name']))
List中还具有和数组一样的方法,二者之间的区别是List会返回一个新的immutable对象

修改immutable对象中的数据方法 set/setIn/update/updateIn

let newState1=state.set('name','zhangjijie')
修改多层级
let newState2 = state.setIn(['info','m'],10)
在原有数据上进行更改
let newState3 = state.update('age',cb=>cb+10)
let newState=state.updateIn(['info','m'],cb=>cb+10)
toJS是转换为原生JS的方法
console.log(newState.toJS())

删除immutable对象中的数据方法 remove/removeIn/delete

let newState1 = state.remove('age')
let newState = state.removeIn(['info','m'])

immutable优化组件

PureComponent只能优化简单数据类型的重复渲染,对于复杂数据类型的重复渲染优化不了。

过渡动画组件

在项目中可能会有一些动画效果展示或是页面切换效果, react-transition-group是react的第三方模块,这个模块可以实现动画切换效果

安装第三方
npm i react-transition-group
三个动画组件
CSSTransition      动画组件   他只能有一个子元素,之于后代可以有多个
SwitchTranstion    切换过渡
TransitionGroup    列表动画组件,用来包裹 CSSTransition

使用
import React, { Component } from 'react'
import { CSSTransition } from 'react-transition-group'
import './style/animate.css'
export default class App extends Component {
  state={
    show:true
  }
  render() {
    return (
      <div>
        <CSSTransition
        // 进出场
        in={this.state.show}
        // 动画时长
        timeout={300}
        // 给样式名称添加前缀
        classNames="fade"
        // 出场后删除DOM
        unmountOnExit
        >
          <h3>我是一个内容显示</h3>
          </CSSTransition>
          <hr />
          <button onClick={()=>{
            this.setState(state=>({show:!state.show}))
          }}>切换显示</button>
        
      </div>
    )
  }
}
列表过渡组件
import React, { Component } from 'react'
import { CSSTransition ,TransitionGroup} from 'react-transition-group'
import './style/animate.css'
export default class App extends Component {
  state={
    title:'',
   todos:[]
  }
  onEnter=({key})=>{
    if(key==='Enter'){
      this.setState(state=>({
        todos:[...state.todos,{id:Date.now(),title:state.title}]
      }),()=>{
        this.setState({title:''})
      })
    }
  }
delItem = id=>{
  this.setState(state=>({
    todos:state.todos.filter(item=>item.id!==id)
  }))
}
  
  render() {
    return (
      <div>
        <div>
          <input type="text" value={this.state.title} onChange={e=>this.setState({title:e.target.value.trim()})}
          onKeyUp={this.onEnter}
          />
        </div>
        <TransitionGroup>
        {
          this.state.todos.map(item =>(
            <CSSTransition
            // 进出场
            in={this.state.show}
            // 动画时长
            timeout={300}
            // 给样式名称添加前缀
            classNames="fade"
            // 出场后删除DOM
            unmountOnExit
            >
             <div>
              <span>{item.title}</span>---
              <span onClick={()=>this.delItem(item.id)}>删除</span>
             </div>
              </CSSTransition>
          ))
        }
        </TransitionGroup>
      </div>
    )
  }
}