高效的Redux Reducer,避免不必要的对象拷贝

时间:2016-02-12 17:21:41

标签: dictionary functional-programming ecmascript-6 functor redux

我想我的问题也可以概括为类似

  

是否有惯用的ES6方式:

     

array.map(identity) === array

     

array.filter(i => true) === array

     

{obj..., attr: obj.attr} === obj

我知道,它还没有像ES6那样实现,但是我是否缺少一些可能的语法或简单的辅助函数,以便在不诉诸不可变的lib的情况下使这些属性成为现实?

我使用Babel和新的JS功能,以及不可变的js对象。

我想知道如何使我的Reducer更有效率并减少不必要的对象副本

我对lib(Mori / ImmutableJS)解决方案不感兴趣。

我有一个管理分页列表的reducer。

pages属性实际上是Array[Array[item]]

这是我的减速机:

const initialState = {
  isLoading: false,
  pages: [],
  allStamplesLoaded: false
};

function reducer(state = initialState, event) {

  switch (event.name) {

    case Names.STAMPLE_DELETED:
      return {
        ...state,
        pages: removeStampleFromPages(state.pages,event.data.stampleId)
      };

    case Names.STAMPLE_UPDATED:
      return {
        ...state,
        pages: updateStampleInPages(state.pages,event.data.apiStample)
      };

    case Names.STAMPLE_PAGES_CLEANED:
      return {
        ...initialState,
      };

    case Names.STAMPLE_PAGE_REQUESTED:
      return {
        ...state,
        isLoading: true
      };

    case Names.STAMPLE_PAGE_LOADED:
      const {stamplePage,isLastPage} = event.data;
      return {
        ...state,
        isLoading: false,
        pages: [...state.pages, stamplePage],
        isLastPage: isLastPage
      };

    case Names.STAMPLE_PAGE_ERROR:
      return {
        ...state,
        isLoading: false
      };

    default:
      return state;
  }
}

我也有这些辅助函数:

function removeStampleFromPages(pages,deletedStampleId) {
  return pages.map(page => {
    return page.filter(apiStample => apiStample != deletedStampleId)
  })
}
function updateStampleInPages(pages,newApiStample) {
  return pages.map(page => {
    return updateStampleInPage(page,newApiStample);
  })
}
function updateStampleInPage(page,newApiStample) {
  return page.map(apiStample => {
    if (apiStample.id === newApiStample.id) {
      return newApiStample;
    }
    else {
      return apiStample;
    }
  })
}

正如您所注意到的,每次触发STAMPLE_UPDATED等事件时,我的reducer总是会返回一个新的状态,其中包含一个新的数组数组,即使数组的所有项都不是更新。这会创建不必要的对象复制和GC。

我不想过早地优化它,也不会在我的应用中引入不可变的库,但我想知道是否有任何惯用的ES6方法来解决这个问题?

1 个答案:

答案 0 :(得分:1)

Immutable.js和Mori等不可变数据结构使用了一个聪明的技巧,以避免一直重建整个结构。

策略非常简单:当您向属性更新属性时,更改它并将所有属性从此节点重新包装到根节点。

假设您希望在以下状态下将属性c更改为4

const state1 = {
  a: {
    b: {
      c: 1
    },
    d: [2, 3, 4],
    e: 'Hello'
  }
}

第一步是将c更新为4。之后你需要创建

  1. b的新对象(因为c已更改)
  2. a的新对象(因为b已更改)
  3. 和州的新对象(因为a已更改)。
  4. 您的新状态将如下所示(对象旁边的*表示该对象已重新创建)

    const state2 = *{
      a: *{
        b: *{
          c: 4
        },
        d: [2, 3, 4],
        e: 'Hello'
      }
    }
    

    请注意de未被触及的方式。

    您现在可以验证内容是否正常运行:

    state1 === state2 // false
    state1.a === state2.a // false
    state1.a.b === state2.a.b //false
    state1.d === state2.d // true
    state1.e === state2.e // true
    

    您可能会注意到de之间共享state1state2

    您可以使用类似的策略在您的州内共享信息,而无需一直重新创建一个全新的状态。

    关于你的初步问题:

    array.map(identity) !== array
    array.filter(i => true) !== array
    {obj..., attr: obj.attr} !== obj
    

    答案很简单。

    创建数组或对象时,Javascript VM会在内部为该对象分配标识符。标识符是增量的,因此没有两个数组/对象是相同的。

    对阵列或对象执行身份检查时,仅检查内部标识符是否匹配。

    a = [] // internal identifier 1
    [] // internal identifier to 2
    b = [] // internal identifier 3
    a === b // id 1 === id 3 is FALSE!
    a === a // id 1 === id 1 is TRUE!