Cleaner/shorter 在 Redux 中更新嵌套状态的方法?
Cleaner/shorter way to update nested state in 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;
}
}
有没有更简洁的方法来做到这一点?
UPD:它现在是 ES2018
的一部分
可能会通过 non-standardised yet properties spread syntax:
稍作改进
return {
...state,
notificationBar: {
...state.notificationBar,
open: true,
},
};
虽然可以使用扩展运算符,但还有许多其他方法可以实现相同的结果,甚至不需要未来的 JS 编译器来实现非标准化功能。以下是其他一些选项,排名不分先后。
Return 一个文字
如果您确定您的状态不会增长,那么您可以简单地 return 整个新状态作为文字。
return {
notificationBar: {
open: true
}
}
但是,这通常并不合适,因为您的状态不太可能如此简单。
合并减速器
Redux 为您提供了一种实用方法,用于组合多个在状态对象的不同部分上工作的 reducer。在这种情况下,您将创建一个单独处理此对象的 notificationBar
reducer。
createStore(combineReducers({
notificationBar: function(state=initialNBarState, action) {
switch (action.type) {
case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
return Object.assign({}, state, { open: true });
}
});
这使您不必担心顶级属性,从而避免对 Object.assign
.
的嵌套调用
如果您的状态可以在逻辑上分解为明确定义的部分,那么这可能是解决此问题的最惯用的方法。
使用不可变数据
您可以使用持久数据结构库来创建可以修改为 return 副本的数据结构。
森
Mori是将Clojure的数据结构和函数API编译成JS的结果
import { hashMap, updateIn } from 'mori';
const initialState = hashMap(
"notificationBar", hashMap(
"open", false
)
);
// ...
return updateIn(state, ['notificationBar', 'open'], true);
ImmutableJS
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 长期以来一直有一套内置的不变性助手。他们使用与 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
的包装器,并提供更实用的语法来更新(嵌套)属性。
您传递的不是新值,而是采用旧值的函数,return是新值。
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);
除了前面所说的,这里还有一个功能性的方法 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
您可以使用镜头。
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 视为组合 属性 access/update。
一些关于镜头的好资源:
- 视频 Lenses Quick n' Dirty
- 实用篇Lenses and Virtual DOM Support Open Closed
- 视频 Functional programming patterns for the non-mathematician
- 文章关于结合Lenses with imutable.js
如果您可以阅读 lisps,我还建议您阅读这篇来自 [=15= 的优秀 镜头介绍 ].
这里的所有建议都很好而且有效,但我想提供另一种解决方案。这里出现的问题绝对是一个常见的模式,所以我认为最好只为此类更新编写自己的接口并坚持在 reducer 中,并使用一个函数在所有 reducer 中深入更新。
例如,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],
});
就是这样——它将在任意嵌套时正确更新。此外,tile 提供了选择器,因此您不必亲自担心数据的确切位置,您可以直接使用它们。
所以,我不想说这是最好的解决方案,但想法很简单——不要害怕编写自己的实现,然后使用工厂来解决这个问题。
如果您正在使用 Immutable.js,您可以在 嵌套结构 主题下查看一些可能对您有帮助的功能,我个人使用 mergeDeep
:
prevState.mergeDeep({ userInfo: {
username: action.payload.username,
} }),
有时减速器会变得有点混乱:
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;
}
}
有没有更简洁的方法来做到这一点?
UPD:它现在是 ES2018
的一部分可能会通过 non-standardised yet properties spread syntax:
稍作改进return {
...state,
notificationBar: {
...state.notificationBar,
open: true,
},
};
虽然可以使用扩展运算符,但还有许多其他方法可以实现相同的结果,甚至不需要未来的 JS 编译器来实现非标准化功能。以下是其他一些选项,排名不分先后。
Return 一个文字
如果您确定您的状态不会增长,那么您可以简单地 return 整个新状态作为文字。
return {
notificationBar: {
open: true
}
}
但是,这通常并不合适,因为您的状态不太可能如此简单。
合并减速器
Redux 为您提供了一种实用方法,用于组合多个在状态对象的不同部分上工作的 reducer。在这种情况下,您将创建一个单独处理此对象的 notificationBar
reducer。
createStore(combineReducers({
notificationBar: function(state=initialNBarState, action) {
switch (action.type) {
case actions.LAYOUT_NOTIFICATIONBAR_OPEN:
return Object.assign({}, state, { open: true });
}
});
这使您不必担心顶级属性,从而避免对 Object.assign
.
如果您的状态可以在逻辑上分解为明确定义的部分,那么这可能是解决此问题的最惯用的方法。
使用不可变数据
您可以使用持久数据结构库来创建可以修改为 return 副本的数据结构。
森
Mori是将Clojure的数据结构和函数API编译成JS的结果
import { hashMap, updateIn } from 'mori';
const initialState = hashMap(
"notificationBar", hashMap(
"open", false
)
);
// ...
return updateIn(state, ['notificationBar', 'open'], true);
ImmutableJS
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 长期以来一直有一套内置的不变性助手。他们使用与 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
的包装器,并提供更实用的语法来更新(嵌套)属性。
您传递的不是新值,而是采用旧值的函数,return是新值。
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);
除了前面所说的,这里还有一个功能性的方法 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
您可以使用镜头。
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 视为组合 属性 access/update。
一些关于镜头的好资源:
- 视频 Lenses Quick n' Dirty
- 实用篇Lenses and Virtual DOM Support Open Closed
- 视频 Functional programming patterns for the non-mathematician
- 文章关于结合Lenses with imutable.js
如果您可以阅读 lisps,我还建议您阅读这篇来自 [=15= 的优秀 镜头介绍 ].
这里的所有建议都很好而且有效,但我想提供另一种解决方案。这里出现的问题绝对是一个常见的模式,所以我认为最好只为此类更新编写自己的接口并坚持在 reducer 中,并使用一个函数在所有 reducer 中深入更新。
例如,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],
});
就是这样——它将在任意嵌套时正确更新。此外,tile 提供了选择器,因此您不必亲自担心数据的确切位置,您可以直接使用它们。 所以,我不想说这是最好的解决方案,但想法很简单——不要害怕编写自己的实现,然后使用工厂来解决这个问题。
如果您正在使用 Immutable.js,您可以在 嵌套结构 主题下查看一些可能对您有帮助的功能,我个人使用 mergeDeep
:
prevState.mergeDeep({ userInfo: {
username: action.payload.username,
} }),