美文网首页Redux思想程序员Web前端之路
翻译|Immutable.js,持久化数据结构和结构共享

翻译|Immutable.js,持久化数据结构和结构共享

作者: smartphp | 来源:发表于2017-03-16 09:33 被阅读140次

    为什么要用Immutable.js来代替Javascript的对象

    翻译版本,原文请见


    把你的数据看成是不可变的会带来很多的好处.实际上在React背后有个原则:React的元素是不可变的.你可能也会对学习不可变App构架有很大兴趣.

    但是使用Immutable.js的好处是:

     function toggleTodo (todos, id) {
      return todos.update(id,
        (todo) => todo.update('completed',//update是immutable的方法
          (completed) => !completed
        )
      )
    }
    

    跳过使用普通的Javascript对象(把他们看作为immutable,可以选则使用例如seamless-immutable之类的助手函数),像这样?:

     function toggleTodo (todos, id) {
      return Object.assign({ }, todos, {
        [id]: Object.assign({ }, todos[id], {
          completed: !todos[id].completed
        })
      })
    }
    // Using updeep
    function toggleTodo (todos, id) {
      return u({
        [id]: {
          completed: (completed) => !completed
        }
      }, todos)
    }
    

    一个非常大的对象...

    让我们假设todo list 里面有100,00个任务:

     var todos = {
      ⋮
      t79444dae: { title: 'Task 50001', completed: false },
      t7eaf70c3: { title: 'Task 50002', completed: false },
      t2fd2ffa0: { title: 'Task 50003', completed: false },
      t6321775c: { title: 'Task 50004', completed: false },
      t2148bf88: { title: 'Task 50005', completed: false },
      t9e37b9b6: { title: 'Task 50006', completed: false },
      tb5b1b6ae: { title: 'Task 50007', completed: false },
      tfe88b26d: { title: 'Task 50008', completed: false },
      ⋮
      (100,000 items)
    }
    

    我刚刚完成第50005件任务.
    现在我想把它标记位完成.

    使用普通Javascript 对象

    var nextState=toggleTodo(todos,'t2148bf88')

    这个单一的操作哟花费134ms来运行.

    为什么?因为当你使用Object.assign,Javascript的浅复制拷贝每一个源的每个属性到目的地.一次一个.

    我们有100,000个todos,所以意味着有100,000个属性要拷贝.
    这就是为什么要花这么长的时间.


    为什么要这么做?

    在Javascript中,对象默认是可以突变(mutable)的.
    当你克隆一个对象,Javascript有每一个属性的拷贝,所以两个对象变得完全分离的.看下图


    100,000个属性被(浅)复制到目的地100,000个属性被(浅)复制到目的地

    这就允许你在拷贝以后改变任何对象的属性,对象之间也不会相互影响.甚至在把这些对象处理为不可变(immutable),Javascript也还是按照mutable来处理.


    使用Immutable.js

     var todos = Immutable.fromJS({
      ⋮
      t79444dae: { title: 'Task 50001', completed: false },
      t7eaf70c3: { title: 'Task 50002', completed: false },
      t2fd2ffa0: { title: 'Task 50003', completed: false },
      t6321775c: { title: 'Task 50004', completed: false },
      t2148bf88: { title: 'Task 50005', completed: false },
      t9e37b9b6: { title: 'Task 50006', completed: false },
      tb5b1b6ae: { title: 'Task 50007', completed: false },
      tfe88b26d: { title: 'Task 50008', completed: false },
      ⋮
      (100,000 items)
    })
    

    使用Immutable.Map来代表我们的数据,更新第50005条任务
    var nextState=toggleTodo(todos,'t2148bf88')

    这个操作仅花费1.2ms时间去运行.速度提升了100倍以上!

    为什么会这么快?

    持久数据结构

    持久数据结构(Persistent data structures)强力限制所有的操作都要返回数据结构的新版本,保持原数据结构的完整性,不能更改原数据结构.

    这一点暗示所有的持久化数据都是不可变的.

    在这个给定的限制下,实现持久化数据结构的库可以进行很好的优化,因为这些库知道我们不会改变我们的数据.

    让我们看一个优化

    使用tries来优化

    为了直观一点,试一个小例子

    想象存储一个键-值映射:

    我们可以把这个数据结构存储到单一的Javascirpt对象中:

     const data = {
      to: 7,
      tea: 3,
      ted: 4,
      ten: 12,
      A: 15,
      i: 11,
      in: 5,
      inn: 9
    }
    

    但是我们怎么才能创建一个trie来代替js的对象呢?他的结构看起来是这个样子的:


    基本上你可根据图上的路径从root开始获取到你需要的值.

    如果你从root开始找data.in,根据标记in的路径.可以找到包含5的节点.

    那么,怎么修改呢?

    让我们思考一下把键tea的值从3改为14.

    我们可以创建一个新的trie,尽可能的使用存在的节点.

    老的树形结构仍然存在,而且没有变化.在实例中你可以保留一个引用

    在上图中如绿色部分所示,我们仅仅只需要更新4个节点来更新这个数.其他的节点是可以重新利用的。

    下面这个图展示Immutable.js怎么实施Immutable.Map.创建一个每个节点有32个分支的树.

    Immutable.Map的实施Immutable.Map的实施

    当我们更新一个单个项目,仅仅只要一些节点需要被重新创建.

    Immutable.js借助crazy advanced techniques保持树形结构的紧凑,根据各种子树的各种属性来创建多种类型的节点.

    并不总是如此...

    不要把本问的本意理解为“你总是需要Immutable.js“.不是这个意思,我只是想强调一下他的好处.解释一下为什么推荐要使用他.

    数据结构是很重要的,但是当我编写软件的时候,我首先要尝试最简单的方式.我过去使用数组和对象,之后当我需要速度提升的时候,我使用Immutable.js,或者是在我遇到到我需要他的时候.在只要少数的条目,还有小的对象和集合的时候,我就不会使用Immutable.js.

    是不是意思是我可能会返回去并且在后面在改变?

    对!非常好!如果你的数据接入是通过单一,组织良好的模块.例如:

     // -- Todos.js --
    export const empty = { }
    export function add (todos, id, todo) {
    return Object.assign({ }, todos, { [id]: todo })
    }
    export function getById (todos, id) {
    return todos[id]
    }
    

    估计所有的应用代码总是要使用这个模块来获取数据.当你想改变内含的数据结构时,你仅仅需要更新这个文件.

    这个我们叫做”实体模块“,封装了代表整个软件系统的所有内容.这个概念来自”Clean Architecture“.我计划以后来写写这个问题.

    不要把应用的逻辑和数据结构耦合在一起

    我很了解应用逻辑和数据结构不耦合在一起的艰难之处.这是因为我们不知道在未来数据怎么来获取.

    例如:我们的todo app现在管理着100,000任务.我们改为使用Immytable.js.现在每个部分都足够好和足够快.

    突然需求来了:”任务要有一个安排者”.(类似老师布置作业给学生),”用户应该可以看到任务是谁给安排的”.

     function findByAssigneeAsArray (todos, assigneeId) {
      return todos.filter(
        (task) => Task.containsAssignee(task, assigneeId)
      ).toArray()
    }
    

    用户开始抱怨app变慢了,分析揭示上面这个函数是个大问题.

    使用上面这段代码需要序列搜索100,000任务.这样做怎么能快的起来?

    要优化这个案例,我们需要改变内在的数据结构

    这需要保持一个反向的查询表,连接任务安排人和任务列表的TaksID.这个优化的修改实例来自于[Taskworld](https://taskworld.com/).

    如果我们的reducer/selector/view代码和数据结构直接连系在一起,要做出这样的改变非常难.

    所以,如果我们想快速迭代,我们需要确保很容易做出修改.从开始就保持代码整洁,书写测试,建立持续集成.

    感谢阅读!

    更多的讨论在Reddit.

    相关文章

      网友评论

        本文标题:翻译|Immutable.js,持久化数据结构和结构共享

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