如何在Redux reducer中以不可变的方式交换数组元素?

9
相关的Redux状态包括一个表示图层的对象数组。
例子:
let state = [
    { id: 1 }, { id: 2 }, { id: 3 }
]

我有一个名为moveLayerIndex的Redux action:
actions.js
export const moveLayerIndex = (id, destinationIndex) => ({
    type: MOVE_LAYER_INDEX,
    id,
    destinationIndex
})

我希望reducer通过交换数组中元素的位置来处理这个action。 reducers/layers.js
const layers = (state=[], action) => {
    switch(action.type) {
        case 'MOVE_LAYER_INDEX':

/* 我应该在这里放什么才能让下面的测试通过 */
        default:
         return state
    }
}

这个测试验证了Redux reducer以不可变的方式交换数组元素。使用Deep-freeze检查初始状态是否没有被改变。你如何让这个测试通过?请在test/reducers/index.js中进行操作。
import { expect } from 'chai'
import deepFreeze from'deep-freeze'

const id=1
const destinationIndex=1

 it('move position of layer', () => {
    const action = actions.moveLayerIndex(id, destinationIndex)
    const initialState = [
        {
          id: 1
        },
        {
          id: 2
        },
        {
          id: 3
        }
    ]
    const expectedState = [
        {
          id: 2
        },
        {
          id: 1
        },
        {
          id: 3
        }
    ]
    deepFreeze(initialState)
    expect(layers(initialState, action)).to.eql(expectedState)
 })
3个回答

12

不可变更新的一个关键思想是,虽然您应该永远不要直接修改原始项,但在返回副本之前对副本进行突变是可以的。

因此,请使用以下函数,它应该能够满足您的需求:

function immutablySwapItems(items, firstIndex, secondIndex) {
    // Constant reference - we can still modify the array itself
    const results= items.slice();
    const firstItem = items[firstIndex];
    results[firstIndex] = items[secondIndex];
    results[secondIndex] = firstItem;

    return results;
}

我为Redux文档编写了一个名为“Structuring Reducers - Immutable Update Patterns”的部分,其中提供了更新数据的一些相关方法的示例。


应该返回items数组吗? - Dez
1
嗯,不应该返回 items,而是应该返回 tempArray,但肯定要修改的是 tempArray,我会修复它。 - markerikson
对不起,是我的错!我看错了函数。你的函数按预期运行。 - Dez

6

您可以使用map函数进行交换:

function immutablySwapItems(items, firstIndex, secondIndex) {
    return items.map(function(element, index) {
        if (index === firstIndex) return items[secondIndex];
        else if (index === secondIndex) return items[firstIndex];
        else return element;
    }
}

在 ES2015 风格中:

const immutablySwapItems = (items, firstIndex, secondIndex) =>
  items.map(
    (element, index) =>
      index === firstIndex
        ? items[secondIndex]
        : index === secondIndex
        ? items[firstIndex]
        : element
  )

我认为这不是一个好的方法... 当你实际上不需要遍历数组(复杂度O(1))时,你正在遍历它(复杂度O(n))。 - John Bernardsson
2
嗨@JohnBernardsson,感谢您的评论。您说的简单交换是常数,而返回新数组则是线性的,这一点您是正确的。但是,如果检查一下问题,它是关于以不可变的方式进行操作的,这意味着不改变当前数组,而是返回一个新的数组。几乎所有在数组中进行不可变性操作的都至少是线性的。请查看以下介绍,了解不可变性思想的简要说明:https://facebook.github.io/immutable-js/docs/#/ - Geraldo B. Landre
我的观点是,被接受的答案中展示的示例也以不可变的方式完成了,并且没有线性复杂度。但是,确实我忽略了示例中“slice()”操作的复杂度,它将是O(n),因为它会复制整个数组。所以我收回我的话.. 哎呀! - John Bernardsson

2

其他两个答案都没有问题,但我认为使用ES6甚至有更简单的方法。

const state = [{
  id: 1
}, {
  id: 2
}, {
  id: 3
}];

const immutableSwap = (items, firstIndex, secondIndex) => {
  const result = [...items];
  [result[firstIndex], result[secondIndex]] = [result[secondIndex], result[firstIndex]];
  return result;

}

const swapped = immutableSwap(state, 2, 0);

console.log("Swapped:", swapped);
console.log("Original:", state);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接