美文网首页让前端飞React.js前端开发
Immutable简介及在React应用中的价值

Immutable简介及在React应用中的价值

作者: A长安 | 来源:发表于2019-04-20 17:57 被阅读6次

    什么是Immutable?

    官网上是这么说的:

    Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

    简单的说就是immutable是一种一旦创建就不能更改的数据类型,这样你在程序中就不用考虑每次对象的深浅拷贝,immutable有高级的数据更改检测机制。我们可以通过它提供的API进行数据的更改,且每次都是返回一个新的对象。

    immutable.js每次进行数据操作都会返回一个新的对象,为了避免深度拷贝带来的性能损耗,immutable使用结构共享,就是说当其中一个节点发生改变之后,更改的只有这个节点本身和它的父节点们,其他节点数据则进行共享。

    具体的操作API可以自行查看immutable.js官网https://immutable-js.github.io/immutable-js/

    那immutable对于react应用的意义有什么呢??

    React组件的重新渲染机制

    1.当调用setState进行state的更改的时候
    2.当父组件传递给子组件的props值改变的之后(更准确的说应该是父组件的重新渲染会带动子组件重新渲染,不管你props有没有变)

    这里有个问题,其实第一条也不是完全准确的,因为当你调用setState的时候即便你state值没有改变也一样会触发re-render,请看代码:

    import React, { Component } from 'react';
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          a: 1
        }
      }
      render() {
        console.log('我render了');
        return (
          <div className="App">
            <button onClick={() => {
              const oldState = this.state;
              this.setState({
                a: oldState.a
              });
            }}>click</button>
            <span>{this.state.a}</span>
          </div>
        );
      }
    }
    export default App;
    

    我每一次的点击即便没有更改state,但是还是会触发render中的console方法!!(这个地方还有待研究),初步理解为调用setState无论更改state与否,都会触发re-render.

    显然上面的渲染操作是我们不希望看到的,这是因为在react组件中shouldComponentUpdate总是返回true的.
    要解决上述问题就需要在shouldComponentUpdate中进行两次state的比较,简单的数据结构比较是很容易的,如果数据结构层级很深,那样的话比较起来就会很麻烦。

    Immutable解决此问题:

    因为immutable是结构不可变的数据类型,每次操作更改之后都会返回一个新的对象,正要是可以大大减少我们diff的过程,只需要比较两个对象在内存中的引用是否相等就可以啦。

    Immutable优点:

    1.安全的操作state:

    react不允许我们直接修改state,但是我们是可以通过setState来修改state。但是setState执行newState和oldState的merge过程是属于浅merge,并不会去深层的递归merge,那浅merge是什么意思呢?看下面代码:

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          a: {
            b: 2,
            c: 3
          }
        }
      }
      render() {
        console.log(this.state.a);
        return (
          <div className="App">
            <button onClick={() => {
              this.setState({
                a: {
                  b: 2
                }
              });
            }}>click</button>
          </div>
        );
      }
    }
    

    可以看到这就是浅merge结果,第二次的设置state会把第一次的state覆盖掉,他定不会通过比较来决定那个字段需要覆盖掉。在实际的应用中,如果state不能deep merge的话,那么受到很大的限制,如果进行深度合并的话,一方面会带来效率上的问题,而且实现起来也比较复杂。同时这种浅merge,还有可能因为一个疏忽导致有用的字段被覆盖掉,造成程序的安全问题。

    是引用immutable可以很有效的避免这个问题,因为每次操作数据返回的都已一个新对象,只需要用过immutable的set方法修改需要被改变的值,来返回一个新的state对象替换掉老的state对象,使我们操作state更加的安全。

    2.节省内存

    immutable.js使用了Structure Sharing,这可尽可能的复用内存,而且实现时间旅行功能也是很简单的。

    let a = Immutable.Map({
      select: 'user_a',
      filter: Immutable.Map({ name: 'Cam' })
    })
    let b = a.set('select', 'user_b');
    
    a === b; // false
    a.get('filter') === b.get('filter'); // true
    

    解释一下上代码的过程,首先创建了一个Map对象赋值给变量啊a, 然后a通过set方法修改select属性,新生成的Immutable对象赋值给b,虽然a,b是两个不同的对象,但是a和b共享了没有改变的filter节点,所以a.get('filter') 等于 b.get('filter')。

    **Immutable为了节省性能和内存还会避免创建新的对象,如果数据没有改变的话它是不会创建新对象的: **

    const originalMap = Immutable.Map({  b: 2 })
    const updatedMap = Immutable.originalMap.set('b', 2)
    updatedMap === originalMap // return ture
    

    就是因为操作都会产生一个全新的Immutable对象,所以我认为可以将每次操作的产物存储在一个数组中,这个样就可以轻松拿到之前版本的数据,来完成时间旅行,版本回退的功能了。

    3.拥抱函数式编程

    Immutable本身就是函数式编程中的概念,即每次的输入相同输出的结果必然相同,个人认为这个编程里面要比面想对象的编程对应用更加的安全,也方便团队的合作和代码的调试。

    Immutable的缺点:

    1.增加学习成本

    使用immutable和使用原生的js对象有很大的不同,比如在immutable中就不能够使用解构赋值,和对象运算符,取而代之的是使用immutable中get,set等api。但是immutable的常用API通过官网学习也是很简单掌握的。

    2.侵入性强

    当我们使用第三方组件库的时候将不得不将immutable通过toJS方法将其转为原生的JS对象。

    3.容易与原生对象混淆

    在实际开发过程中通常有些人会把immutable对象和原生JS对象用混,这个问题其实是可以通过命名规则的方法来解决,例如immutable对象我们可以给其加一个$前缀:

    let $obj = Immutable.fromJS({a: 1}) // immutable对象
    let obj = {a: 1} // 原生对象
    

    但是还是建议在程序用如果引用了immutable就尽量不要使用原生的JS对象,除非特殊的情况不得已将immutable对象转为原生JS进行操作。因为两种对象互相转换其实还是挺耗费性能的,尽量避免这种转换。

    总结:

    当项目变得复杂时,每一次action对于生成的新state都会消耗一定的性能,所以为了防止不符合预期的re-render,减少不必要的render提高react的性能,可以通过immutable来最大实现符合我们预期的re-render。由于侵入性较强,新项目引入比较容易,老项目迁移需要评估迁移。对于一些提供给外部使用的公共组件,最好不要把 Immutable 对象直接暴露在对外接口中。

    相关文章

      网友评论

        本文标题:Immutable简介及在React应用中的价值

        本文链接:https://www.haomeiwen.com/subject/anxgiqtx.html