更新/更短的方式来更新Redux中的嵌套状态?

时间:2016-02-24 02:20:35

标签: javascript reactjs redux

有时候减速器会变得混乱:

const initialState = {
    notificationBar: {
        open: false,
    },
};

export default function (state = initialState, action) {
  switch (action.type) {
    case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
      return Object.assign({}, state, {
        // TODO: Find a cleaner way to do this!
        notificationBar: Object.assign({}, state.notificationBar, {
          open: true,
        }),
      });
    default:
      return state;
  }
}

有更简洁的方法吗?

6 个答案:

答案 0 :(得分:46)

虽然可以使用扩展运算符,但是有很多其他方法可以实现相同的结果,甚至不需要将来的JS编译器用于非标准化功能。以下是一些其他选项,没有特别的顺序。

返回文字

如果您确定自己的状态不会增长,那么您只需将整个新状态作为文字返回。

return {
  notificationBar: {
    open: true
  }
}

但是,这通常不合适,因为你的州不太可能这么简单。

组合减速器

Redux为您提供了一种实用工具方法,用于组合多个适用于状态对象不同部分的Reducer。在这种情况下,您将创建一个仅处理此对象的notificationBar缩减器。

 createStore(combineReducers({
   notificationBar: function(state=initialNBarState, action) {
     switch (action.type) {
       case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
         return Object.assign({}, state, { open: true });
   }
 });

这可以防止您不必担心顶级属性,这样就可以避免将调用嵌套到Object.assign

如果您的状态可以在逻辑上划分为明确定义的部分,那么这可能是解决此问题的最常用方法。

使用不可变数据

您可以使用持久性数据结构库来创建数据结构,而不是可以修改以返回副本。

Mori是将Clojure的数据结构和功能API编译成JS的结果。

import { hashMap, updateIn } from 'mori';

const initialState = hashMap(
  "notificationBar", hashMap(
    "open", false
  )
);

// ...

return updateIn(state, ['notificationBar', 'open'], true);

ImmutableJS

ImmutableJS是将Hlo Array Mapped Tries的语义从Clojure的持久数据结构带入Javascript的更为迫切的方法。

import { Map } from 'immutable';

const initialState = Map({
  notificationBar: Map({
    open: true
  });
});

// ...

return state.setIn(['notificationBar', 'open'], true);

别名Object.assign

您可以创建更友好的Object.assign版本来编写上述代码的更新版本。实际上,它几乎与...运算符一样简洁。

function $set(...objects) {
  return Object.assign({}, ...objects);
}

return $set(state, {
  notificationBar: $set(state.notificationBar, {
    open: true,
  })
});

使用不可变助手

有许多库也提供不变性助手,可以对常规可变对象进行修改。

反应-插件更新

React长期拥有一套内置的不变性助手。它们使用与MongoDB查询类似的语法。

import update from 'react-addons-update';

return update(state, {
  notificationBar: {
    open: { $set: true }
  }
});

点 - 丙不可改变

此库允许您使用熟悉的点路径指定(嵌套)属性的更新。

import dotProp from 'dot-prop-immutable';

return dotProp.set(state, 'notificationBar.open', true);

更新式

这个库是react-addons-update的包装器,为更新(嵌套)属性提供了更多功能的语法。

不是传递新值,而是传递一个函数,该函数接受旧值并返回一个新值。

import updateIn from 'update-in';

return updateIn(state, ['notificationBar', 'open'], () => true);

不可改变路径

为了更新属性,此库就像dot-prop-immutableupdate-in之间的交叉。

import path from 'immutable-path';

return path.map(state, 'notificationBar.open', () => true);

答案 1 :(得分:43)

UPD :它现在已成为ES2018的一部分

通过non-standardised yet properties spread syntax

可能略有改进
list-style-type: disc !important;
list-style: disc !important;
list-style-position:inside !important;
list-style-image: url("http://ils.unc.edu/~ferrerih/web-dev-project/li-red-bullet-smaller.png") !important;

答案 2 :(得分:6)

你可以使用镜头。

import { set, makeLenses } from '@DrBoolean/lenses'

const L = makeLenses(['notificationBar', 'open']);
const notificationBarOpen = compose(L.notificationBar, L.open)
const setNotificationBarOpenTrue = set(notificationBarOpen, true)

const a = { notificationBar: { open: false } }
const b = setNotificationBarOpenTrue(a) 
// `a` is left unchanged and `b` is `{ notificationBar: { open: true } }`

您可以将镜头视为组合属性访问/更新。

关于镜片的一些好资源:

如果您可以阅读 lisps ,我还建议您从racket docs查看这个优秀的镜片介绍。最后,如果你想深入阅读 haskell ,你可以观看:Lenses - compositional data access and manipulation

答案 3 :(得分:1)

如果您正在使用Immutable.js,可以在嵌套结构主题下查看一些可能对您有帮助的功能,我个人使用mergeDeep

prevState.mergeDeep({ userInfo: {
  username: action.payload.username,
} }),

答案 4 :(得分:0)

除了前面已经说过的内容之外,这里还有Ramda的功能方式:

import { assocPath } from 'ramda';

const o1 = { a: { b: { c: 1 }, bb: { cc: 22 } } };
const o2 = assocPath(['a', 'b', 'c'])(42)(o1);
console.log(o1 !== o2, o1.a !== o2.a); // new copies of "changed" objects
console.log(o1.a.bb === o2.a.bb); // deep unchanged properties are copied by reference

答案 5 :(得分:0)

这里的所有建议都很棒且有效,但我想提供另一种解决方案。这里出现的问题绝对是一种常见的模式,所以我认为只为自己创建这样的更新接口并在Reducer中使用它更好,并使用一个函数在所有reducers中深入更新。

例如,I have created a library,我试图通过下一个方式解决这个问题:我得到模块的类型(所谓的“tile”),执行操作的函数(同步和同步)和基于传递的参数进行所需的嵌套。因此,对于您的情况,它将类似于:

import { createSyncTile } from 'redux-tiles';
const uiTile = createSyncTile({
  type: ['ui', 'elements'],
  fn: ({ params }) => params,
  // type will be `notificationBar`
  nesting: ({ type }) => [type],
});

就是这样 - 它将在任意嵌套上正确更新。此外,磁贴提供选择器,因此您不必担心精确数据的位置,您可以使用它们。 所以,我不想说它是最好的解决方案,但这个想法非常简单 - 不要害怕编写自己的实现,然后只使用工厂来解决这个问题。