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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35592078/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 17:56:42  来源:igfitidea点击:

Cleaner/shorter way to update nested state in Redux?

javascriptreactjsredux

提问by ffxsam

Sometimes reducers get kind of messy:

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

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

Is there a more terse way to do this?

有没有更简洁的方法来做到这一点?

回答by zerkms

UPD: it's now a part of the ES2018

UPD:它现在是 ES2018 的一部分

It might be slightly improved via a non-standardisedyet properties spread syntax:

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

return {
    ...state,
    notificationBar: {
        ...state.notificationBar,
        open: true,
    },
};

回答by Dan Prince

Although it's possible to use the spread operator, there are lots of other ways to achieve the same result without even needing a future JS compiler for a non standardised feature. Here are some other options in no particular order.

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

Return a Literal

返回文字

If you are sure that your state won't grow, then you can simply return the entire new state as a literal.

如果您确定您的状态不会增长,那么您可以简单地将整个新状态作为文字返回。

return {
  notificationBar: {
    open: true
  }
}

However, that's not often going to be appropriate because it's unlikely that your state will be this simple.

但是,这通常并不合适,因为您的状态不太可能如此简单。



Combine Reducers

组合减速器

Redux gives you a utility method for combining several reducers that work on different parts of the state object. In this case, you'd create a notificationBarreducer that handled this object alone.

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

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

This prevents you from having to worry about the top level of properties, so that you can avoid nesting calls to Object.assign.

这使您不必担心顶级属性,从而避免嵌套调用Object.assign.

If your state can logically broken down into clearly defined sections then this is probably the most idiomatic way to solve this problem.

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



Use Immutable Data

使用不可变数据

You can use an persistent data structures library to create data structures than can be modified to return a copy.

您可以使用持久数据结构库来创建可以修改以返回副本的数据结构。

Mori

Mori is the result of compiling Clojure's data structures and functional API into JS.

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

import { hashMap, updateIn } from 'mori';

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

// ...

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

ImmutableJS

不可变JS

ImmutableJS is a more imperative approach to bringing the semantics of Hash Array Mapped Tries from Clojure's persistent data structures to Javascript.

ImmutableJS 是一种将哈希数组映射尝试的语义从 Clojure 的持久性数据结构引入 Javascript 的更为必要的方法。

import { Map } from 'immutable';

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

// ...

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


Alias Object.assign

别名 Object.assign

You can create a friendlier version of Object.assignto write terser versions of the code above. In fact, it can be nearly as terse as the ...operator.

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

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

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


Use Immutable Helpers

使用不可变的助手

There are a number of libraries that also offer immutability helpers for making modifications to regular mutable objects.

有许多库还提供了不变性助手,用于对常规可变对象进行修改。

react-addons-update

反应插件更新

React has had a built in set of immutability helpers for a long time. They use a similar syntax to MongoDB queries.

很长一段时间以来,React 都有一组内置的不变性助手。它们使用与 MongoDB 查询类似的语法。

import update from 'react-addons-update';

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

dot-prop-immutable

点属性不可变

This library allows you to use familiar dot paths to specify updates to (nested) properties.

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

import dotProp from 'dot-prop-immutable';

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

update-in

更新

This library is a wrapper around react-addons-updateand provides a more functional syntax for updating (nested) properties.

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

Rather than passing a new value, you pass a function which takes the old value and returns a new one.

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

import updateIn from 'update-in';

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

immutable-path

不可变路径

For updating properties, this library is like a cross between dot-prop-immutableand update-in.

对于更新属性,这个库就像是dot-prop-immutable和之间的交叉update-in

import path from 'immutable-path';

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

回答by Safareli

You can use Lenses.

您可以使用镜头。

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

You can think about Lenses as compositional property access/update.

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

Some good resources about Lenses:

一些关于镜头的好资源:

If you are ok with reading lisps, I would also recommend taking look at this excellent introduction to lensesfrom racket docs. Finally, if you want to go deeper and are ok with reading haskellyou can watch: Lenses - compositional data access and manipulation.

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

回答by Ricardo Mutti

If you're using Immutable.js, you can look under Nested Structurestopic some functions that may help you, I personally use mergeDeep:

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

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

回答by Bloomca

All advice here are great and valid, but I would like to offer another solution. The problem which appears here is definitely a common pattern, so I think it is much better just to write your own interface for such updates and stick with it inside reducers, and use one function to update deeply inside all your reducers.

这里的所有建议都很好且有效,但我想提供另一种解决方案。这里出现的问题绝对是一个常见的模式,所以我认为最好为此类更新编写自己的接口并在reducer内部坚持使用它,并使用一个函数在所有reducer内部进行深入更新。

For example, I have created a library, where I tried to address this issue the next way: I get the type of the module (so-called "tile"), function to perform operations (both async and sync) and desired nesting based on passed params. So, for your case it will be something like:

例如,我创建了一个库,我尝试以下一种方式解决这个问题:我获取模块的类型(所谓的“tile”)、执行操作的函数(异步和同步)和所需的基于嵌套在传递的参数上。因此,对于您的情况,它将类似于:

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

And that's it -- it will be update correctly on arbitrary nesting. Also, tile provides selectors, so you don't have to worry personally where precisely data is located, you can just use them. So, I don't want to say it is the best solution, but the idea is pretty simple -- don't be afraid to write your own implementation, and then just use factory to solve this issue.

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

回答by aeldar

In addition to what've been said earlier, here is a functional way with Ramda:

除了之前所说的,这里还有一个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