在 Redux 中更新嵌套状态的更简洁/更短的方法?

IT技术 javascript reactjs redux
2021-03-05 13:43:22

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

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个回答

UPD:它现在是 ES2018 的一部分

它可能会通过非标准化但属性传播的语法稍微改进

return {
    ...state,
    notificationBar: {
        ...state.notificationBar,
        open: true,
    },
};
@jcubic 我认为这是一个错误:从语言的角度来看,它不能是一个运算符,它是标点符号。我报告了一个问题,谢谢!github.com/tc39/ecma262/issues/1295
2021-04-19 13:43:22
在 ECMAScript 规范 9.0 ecma-international.org/ecma-262/9.0/index.html 的开头他们说“休息参数和传播运算符”,不知道为什么 MDN 文档说“语法”。
2021-04-21 13:43:22
@jcubic 谢谢!很抱歉挑剔,但它不是“操作员”。我误用了这个词,它只是一个“传播语法”(所以我也纠正了我在这方面的答案)
2021-05-14 13:43:22

尽管可以使用扩展运算符,但还有许多其他方法可以实现相同的结果,甚至不需要未来的 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);

不可变JS

ImmutableJS 是一种将哈希数组映射尝试的语义从 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插件更新

很长一段时间以来,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-immutable之间的交叉update-in

import path from 'immutable-path';

return path.map(state, 'notificationBar.open', () => true);
您的 dot-prop-immutable 示例中存在错误.. 应该是return dotProp.set(state, 'notificationBar.open', true);- 缺少 .set。抱歉,如果少于 6 个字符,则不会让我编辑答案。
2021-04-17 13:43:22
还有各种其他类似于 React Helpers 的库,例如 dot-prop-immutable、update-in、immutable-path 和 object-path-immutable。
2021-05-01 13:43:22
当然,但这是表达始终返回的函数的一种非常令人困惑的方式true它不仅更容易阅读和理解,而且绑定函数的性能也更差,更难调试。
2021-05-05 13:43:22
@ffxsam 它用于数组,而不是用于对象。
2021-05-11 13:43:22
Boolean.bind(0,1)对于最后一对,您可以使用箭头/匿名代替箭头...
2021-05-13 13:43:22

您可以使用镜头。

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 } }`

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

一些关于镜头的好资源:

如果你对阅读lisps 没问题,我还建议你看看这个来自球拍文档的优秀镜头介绍最后,如果你想更深入地阅读Haskell,你可以看:镜头 - 合成数据访问和操作

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

prevState.mergeDeep({ userInfo: {
  username: action.payload.username,
} }),
又名 lodash 的 _.merge
2021-04-22 13:43:22

除了搞什么名堂了前面说的,这里是一个功能性的方式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