• 在react/redux中使用Immutable


    有人说 Immutable 可以给 React 应用带来数十倍的提升,也有人说 Immutable 的引入是近期 JavaScript 中伟大的发明,因为同期 React 太火,它的光芒被掩盖了。这些至少说明 Immutable 是很有价值的,下面我们来一探究竟。

    1、什么是Immutable?

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

     

    初识:

    让我们看下面一段代码:

    function keyLog(touchFn) {
      let data = { key: 'value' };
      fun(data);
      console.log(data.key); // 猜猜会打印什么?
    }

    不查看fun方法,不知道它对data做了什么,无法确认会打印什么。但如果data是Immutable,你可以确定打印的就是value:

    function keyLog(touchFn) {
      let data = Immutable.Map({ key: 'value' });
      fun(data);
      console.log(data.get('key'));  // value
    }

    JavaScript中的Object与Array等使用的是引用赋值,如果新的对象简单的引用了原始对象,改变新的对象也将影响旧的。

    foo = {a:1};  bar = foo;  bar.a = 2;
    foo.a // 2

    虽然这样可以节约内存,但当应用复杂后,造成了状态不可控,是很大的隐患,节约内存的优点变得得不偿失。

    Immutable则不一样,相应的:

    foo = Immutable.Map({ a: 1});
    bar = foo.set('a', 2);
    foo.get('a') // 1

     简洁:

    在redux中,它的最优做法是每个reducer都返回一个新的对象(数组),所以我们常常会看到这样的代码:

    // reducer
    ...
    return [
       ...oldArr.slice(0,3),
       newValue,
       ...oldArr.slice(4)   
    ];

    为了返回新的对象(数组),不得不有上面奇怪的样子,而在使用更深的数据结构时会变的更棘手。
    让我们看看Immutable的做法:

    // reducer
    ...
    return oldArr.set(4, newValue);

    是不是很简洁?

    关于"===":

    众所周知,对于Object与Array的===比较,是对引用地址的比较,而不是“值比较”,如:

    {a:1, b:2, c:3} === {a:1, b:2, c:3}; // false
    [1, 2, [3, 4]] === [1, 2, [3, 4]]; // false

    对于上面只能采用deepCopydeepCompare来比较,不仅麻烦而且耗性能。

    我们感受一下Immutbale的做法:

    map1 = Immutable.Map({a:1, b:2, c:3});
    map2 = Immutable.Map({a:1, b:2, c:3});
    Immutable.is(map1, map2); // true 比较值
    map1 === map2; // false 比较地址
    // List1 = Immutable.List([1, 2, Immutable.List[3, 4]]);
    List1 = Immutable.fromJS([1, 2, [3, 4]]);
    List2 = Immutable.fromJS([1, 2, [3, 4]]);
    Immutable.is(List1, List2); // true

    似乎有阵清风吹过。

    Immutable.is 比较的是两个对象的 hashCode 或 valueOf(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。

    Immutable使用了Structure Sharing会尽量复用内存,甚至以前使用的对象也可以再次被复用,引用的对象会被垃圾回收。

    import { Map} from 'immutable';
    let a = Map({
      select: 'users',
      filter: Map({ name: 'Cam' })
    })
    let b = a.set('select', 'people');
    
    a === b; // false
    a.get('filter') === b.get('filter'); // true

    上面 a 和 b 共享了没有变化的 filter 节点。

    并发安全:

    传统的并发非常难做,因为要处理各种数据不一致问题,因此『聪明人』发明了各种锁来解决。但使用了 Immutable 之后,数据天生是不可变的,并发锁就不需要了

    然而现在并没什么卵用,因为 JavaScript 还是单线程运行的啊。但未来可能会加入,提前解决未来的问题不也挺好吗?

    函数式编程:

    Immutable本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

    像 ClojureScript,Elm 等函数式编程语言中的数据类型天生都是 Immutable 的,这也是为什么 ClojureScript 基于 React 的框架 --- Om 性能比 React 还要好的原因。

    2、在react中使用Immutable

    熟悉React的都知道,React做性能优化时有个大招,就是使用shouldComponentUpdate(),但它默认返回true,即始终会执行render()方法,后面做Virtual DOM比较,并得出是都需要做真是DOM更新,这里往往会带来很多务必要的渲染成为性能瓶颈。

    在使用原生属性时,为了得出shouldComponetUpdate正确的true or false,不得不用deepCopydeepCompare来算出答案,但 deepCopy 和 deepCompare 一般都是非常耗性能的

    而在有了Immutable之后,Immutable 则提供了简洁高效的判断数据是否变化的方法,来减少 React 重复渲染,提高性能,只需 === 和 is 比较就能知道是否需要执行 render(),而这个操作几乎 0 成本,所以可以极大提高性能。修改后的 shouldComponentUpdate 是这样的:

    import { is } from 'immutable';
    
    shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
      const thisProps = this.props || {}, thisState = this.state || {};
    
      if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
          Object.keys(thisState).length !== Object.keys(nextState).length) {
        return true;
      }
    
      for (const key in nextProps) {
        if (!is(thisProps[key], nextProps[key])) {
          return true;
        }
      }
    
      for (const key in nextState) {
        if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
          return true;
        }
      }
      return false;
    }

    使用 Immutable 后,如下图,当红色节点的 state 变化后,不会再渲染树中的所有节点,而是只渲染图中绿色的部分:

    setState 的一个技巧

    React 建议把 this.state 当作 Immutable 的,因为修改前需要做一个 deepCopy,显得麻烦:

    import '_' from 'lodash';
    
    const Component = React.createClass({
      getInitialState() {
        return {
          data: { times: 0 }
        }
      },
      handleAdd() {
        let data = _.cloneDeep(this.state.data);
        data.times = data.times + 1;
        this.setState({ data: data });
        // 如果上面不做 cloneDeep,下面打印的结果会是已经加 1 后的值。
        console.log(this.state.data.times);
      }
    }

    使用 Immutable 后:

      getInitialState() {
        return {
          data: Map({ times: 0 })
        }
      },
      handleAdd() {
        this.setState({ data: this.state.data.update('times', v => v + 1) });
        // 这时的 times 并不会改变
        console.log(this.state.data.get('times'));
      }

    上面的 handleAdd 可以简写成:

      handleAdd() {
        this.setState(({data}) => ({
          data: data.update('times', v => v + 1) })
        });
      }

    3、如何在Redux中使用Immutable

    目标:将state -> Immutable化。
    关键的库:gajus/redux-immutable
    将原来 Redux提供的combineReducers改由上面的库提供: 

    // rootReduers.js
    // import { combineReducers } from 'redux'; // 旧的方法
    import { combineReducers } from 'redux-immutable'; // 新的方法
    
    import prop1 from './prop1';
    import prop2 from './prop2';
    import prop3 from './prop3';
    
    const rootReducer = combineReducers({
      prop1, prop2, prop3,
    });
    
    
    // store.js
    // 创建store的方法和常规一样
    import { createStore } from 'redux';
    import rootReducer from './reducers';
    
    const store = createStore(rootReducer);
    export default store;

    通过新的combineReducers将把store对象转化成Immutable,在container中使用时也会略有不同(但这正是我们想要的):

    const mapStateToProps = (state) => ({
      prop1: state.get('prop1'),
      prop2: state.get('prop2'),
      prop3: state.get('prop3'),
      next: state.get('next'),
    });
    export default connect(mapStateToProps)(App);

    4、总结

    Immutable 可以给应用带来极大的性能提升,但是否使用还要看项目情况。由于侵入性较强,新项目引入比较容易,老项目迁移需要评估迁移。对于一些提供给外部使用的公共组件,最好不要把 Immutable 对象直接暴露在对外接口中。

    如果 JS 原生 Immutable 类型会不会太美,被称为 React API 终结者的 Sebastian Markbåge 有一个这样的提案,能否通过现在还不确定。不过可以肯定的是 Immutable 会被越来越多的项目使用。

  • 相关阅读:
    简单的抽奖程序
    WinForm——记住密码
    《Effective C#中文版:改善C#程序的50种方法》读书笔记
    初入博客,给自己的学习定一个目标。
    做IT博客的第一天哈哈
    多表关联查询加内联形式收藏
    UpdateProgress的AssociatedUpdatePanelID属性增加后依然不显示的问题解决(转载)
    SQL Server2005安装找不到SQL Server Management Studio解决办法
    Access中not in优化方式
    App.Config文件中包含中文汉字程序出错的解决办法
  • 原文地址:https://www.cnblogs.com/greatluoluo/p/8469224.html
Copyright © 2020-2023  润新知