如何将方法放到 Redux 状态的对象上?
How to put methods onto the objects in Redux state?
根据 docs React 应用程序的状态必须是可序列化的。
那类呢?
假设我有一个 ToDo 应用程序。
Todo
项中的每一项都具有 name
、date
等属性,到目前为止一切顺利。
现在我想在不可序列化的对象上使用方法。 IE。 Todo.rename()
这将重命名待办事项并做很多其他事情。
据我所知,我可以在某处声明函数并执行 rename(Todo)
或者通过 props this.props.rename(Todo)
将该函数传递给组件。
我在某处声明 .rename()
时遇到 2 个问题:
1)在哪里?在减速机?很难在应用程序周围的 reducer 中找到所有 would be instance
方法。
2) 传递这个函数。真的吗?我应该通过以下方式从所有更高级别的组件手动传递它吗
每次我有更多的方法时,都会添加大量的样板文件来传递它?
或者总是这样做并希望我对一种类型的对象只有一种重命名方法。不是 Todo.rename()
Task.rename()
和 Event.rename()
我觉得这很傻。对象应该知道可以对它做什么以及用什么方式。不是吗?
我在这里缺少什么?
在 Redux 中,您实际上没有自定义模型。您的状态应该是普通对象(或不可变记录)。他们不应有任何自定义方法。
您应该编写处理操作的缩减程序,而不是将方法放到模型上(例如TodoItem.rename
)。这就是 Redux 的全部意义。
// Manages single todo item
function todoItem(state, action) {
switch (action.type) {
case 'ADD':
return { name: action.name, complete: false };
case 'RENAME':
return { ...state, name: action.name };
case 'TOGGLE_COMPLETE':
return { ...state, complete: !state.complete };
default:
return state;
}
}
// Manages a list of todo items
function todoItems(state = [], action) {
switch (action.type) {
case 'ADD':
return [...state, todoItem(undefined, action)];
case 'REMOVE':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1)
];
case 'RENAME':
case 'TOGGLE_COMPLETE':
return [
...state.slice(0, action.index),
todoItem(state[action.index], action),
...state.slice(action.index + 1)
];
}
}
如果这仍然没有意义,请通读 Redux basics tutorial,因为您似乎对 Redux 应用程序的结构有错误的认识。
就从头编写应用程序时应如何使用 redux 而言,Dan 的回答当然是正确的。我的回答是基于一个可能类似于 OP 情况的非理想激励情况以及我正在考虑如何处理它。
我发现自己正在尝试创建一个 redux 应用程序,它依赖于 redux reducer 中的第 3 方库,这些库使用方法对输入对象执行操作,我想将这些输入对象保持在 redux 状态。这是我在伪代码中的激励情况,以及我如何 "solving" 使用 lodash 的分配方法:
// Library A is a large 3rd party library I don't want to change
// Library A expects instances of MyClass to have a method 'complexOperation', and also to have property 'a'
LibraryA = class {
static reliesOnComplexOperation(myClassInstance) {
if (myClassInstance.complexOperation() && myClassInstance.a < 100) {
return true;
} else {
return false;
}
}
}
// MyClass is my own class that I control, and I want to store it's state and make changes to it using the redux reducer.
MyClass = class {
constructor() {
this.a = 0;
}
myComplexOperation() {
return a > 10;
}
}
//and here is my redux reducer, making use of Library A
function reducer(state, action) {
switch (action.type) {
case CHANGE_MY_CLASS:
const myClassInstancePlain = state.myClassInstance;
// redux has converted myClassInstance into a plain object with no methods, so we need to create a new instance of MyClass, and assign property values from the plain object to the new instance. Using the assign function from lodash library to achieve this - likely not the most efficient way
const myClassInstance = _.assign(new MyClass(), myClassInstancePlain);
// now I can pass myClassInstance off to LibraryA, and the complexOperation method will be available
if (LibraryA.reliesOnComplexOperation(myClassInstance)) {
myClassInstance.a = 50;
}
// I can return myClassInstance as part of the new state, and redux will convert it to a plain object
return {
...state,
myClassInstance
};
default:
return state;
}
}
所以这个例子展示了一种方法,将 redux 状态下的对象与方法合并为普通对象,然后添加回方法,以便它们可以在 redux reducer 中使用。很想听听 Dan 或其他人对这种模式的想法,或者如何才能做得更好。我在几个地方看到 Dan post 说用方法存储对象在 redux 中是个坏主意,所以这里的目标是找到一种方法将对象存储为普通对象并在需要时附加方法以一种有效的方式。
我不确定这是否完全相关(上下文是将 Redux 与 React 结合使用),但我在搜索类似内容时发现了此页面,所以我写这篇文章是为了以防万一。
我的动机是,我有状态对象,我想在其中公开该状态的表示而不是状态本身。因此,例如(使用Immutable.js),我有一个状态(模式),它包含一个或多个字段,每个字段由一个唯一的标识符并存储在一个地图中,加上一个标识符列表,它给出了字段排序。
我真的不想写
这样的东西
state.get('field').get(state.get('order').get(nth))
在 reducer 附近以外的任何地方获取第 n 个字段,以便隐藏内部表示并且可以轻松更改。
我目前正在做的(这是一个小实验)是在与减速器相同的文件中添加一个函数:
schema.mapVisibleState = function (state) {
return {
getOrderedFields: () => state.get('order').map((fid) => state.get('fields').get(fid)
}
}
这用于相应的 React 组件(以及具有类似动机的 schema.bindActionCreators 函数):
export const Schema = connect(
(state) => schema.mapVisibleState (state),
(dispatch) => schema.bindActionCreators (dispatch)
)(_Schema) ;
现在,我可以按正确的顺序访问组件内部的字段,就像 this.props.getOrderedFields() 一样,而不用担心底层表示。它还具有额外的优点 属性,即可以轻松地对 getOrderedFields() 函数进行依赖注入。
此外,由于我对字段状态有类似的结构,我可以扩展它:
schema.mapVisibleState = function (state) {
return {
getOrderedFields: () => state.get('order').map((fid) => field.mapVisibleState(state.get('fields').get(fid)),
getNthField (nth) => field.mapVisibleState(state.get('fields').get(state.get('order').get(nth)))
}
}
根据 docs React 应用程序的状态必须是可序列化的。 那类呢?
假设我有一个 ToDo 应用程序。
Todo
项中的每一项都具有 name
、date
等属性,到目前为止一切顺利。
现在我想在不可序列化的对象上使用方法。 IE。 Todo.rename()
这将重命名待办事项并做很多其他事情。
据我所知,我可以在某处声明函数并执行 rename(Todo)
或者通过 props this.props.rename(Todo)
将该函数传递给组件。
我在某处声明 .rename()
时遇到 2 个问题:
1)在哪里?在减速机?很难在应用程序周围的 reducer 中找到所有 would be instance
方法。
2) 传递这个函数。真的吗?我应该通过以下方式从所有更高级别的组件手动传递它吗
每次我有更多的方法时,都会添加大量的样板文件来传递它?
或者总是这样做并希望我对一种类型的对象只有一种重命名方法。不是 Todo.rename()
Task.rename()
和 Event.rename()
我觉得这很傻。对象应该知道可以对它做什么以及用什么方式。不是吗?
我在这里缺少什么?
在 Redux 中,您实际上没有自定义模型。您的状态应该是普通对象(或不可变记录)。他们不应有任何自定义方法。
您应该编写处理操作的缩减程序,而不是将方法放到模型上(例如TodoItem.rename
)。这就是 Redux 的全部意义。
// Manages single todo item
function todoItem(state, action) {
switch (action.type) {
case 'ADD':
return { name: action.name, complete: false };
case 'RENAME':
return { ...state, name: action.name };
case 'TOGGLE_COMPLETE':
return { ...state, complete: !state.complete };
default:
return state;
}
}
// Manages a list of todo items
function todoItems(state = [], action) {
switch (action.type) {
case 'ADD':
return [...state, todoItem(undefined, action)];
case 'REMOVE':
return [
...state.slice(0, action.index),
...state.slice(action.index + 1)
];
case 'RENAME':
case 'TOGGLE_COMPLETE':
return [
...state.slice(0, action.index),
todoItem(state[action.index], action),
...state.slice(action.index + 1)
];
}
}
如果这仍然没有意义,请通读 Redux basics tutorial,因为您似乎对 Redux 应用程序的结构有错误的认识。
就从头编写应用程序时应如何使用 redux 而言,Dan 的回答当然是正确的。我的回答是基于一个可能类似于 OP 情况的非理想激励情况以及我正在考虑如何处理它。
我发现自己正在尝试创建一个 redux 应用程序,它依赖于 redux reducer 中的第 3 方库,这些库使用方法对输入对象执行操作,我想将这些输入对象保持在 redux 状态。这是我在伪代码中的激励情况,以及我如何 "solving" 使用 lodash 的分配方法:
// Library A is a large 3rd party library I don't want to change
// Library A expects instances of MyClass to have a method 'complexOperation', and also to have property 'a'
LibraryA = class {
static reliesOnComplexOperation(myClassInstance) {
if (myClassInstance.complexOperation() && myClassInstance.a < 100) {
return true;
} else {
return false;
}
}
}
// MyClass is my own class that I control, and I want to store it's state and make changes to it using the redux reducer.
MyClass = class {
constructor() {
this.a = 0;
}
myComplexOperation() {
return a > 10;
}
}
//and here is my redux reducer, making use of Library A
function reducer(state, action) {
switch (action.type) {
case CHANGE_MY_CLASS:
const myClassInstancePlain = state.myClassInstance;
// redux has converted myClassInstance into a plain object with no methods, so we need to create a new instance of MyClass, and assign property values from the plain object to the new instance. Using the assign function from lodash library to achieve this - likely not the most efficient way
const myClassInstance = _.assign(new MyClass(), myClassInstancePlain);
// now I can pass myClassInstance off to LibraryA, and the complexOperation method will be available
if (LibraryA.reliesOnComplexOperation(myClassInstance)) {
myClassInstance.a = 50;
}
// I can return myClassInstance as part of the new state, and redux will convert it to a plain object
return {
...state,
myClassInstance
};
default:
return state;
}
}
所以这个例子展示了一种方法,将 redux 状态下的对象与方法合并为普通对象,然后添加回方法,以便它们可以在 redux reducer 中使用。很想听听 Dan 或其他人对这种模式的想法,或者如何才能做得更好。我在几个地方看到 Dan post 说用方法存储对象在 redux 中是个坏主意,所以这里的目标是找到一种方法将对象存储为普通对象并在需要时附加方法以一种有效的方式。
我不确定这是否完全相关(上下文是将 Redux 与 React 结合使用),但我在搜索类似内容时发现了此页面,所以我写这篇文章是为了以防万一。
我的动机是,我有状态对象,我想在其中公开该状态的表示而不是状态本身。因此,例如(使用Immutable.js),我有一个状态(模式),它包含一个或多个字段,每个字段由一个唯一的标识符并存储在一个地图中,加上一个标识符列表,它给出了字段排序。
我真的不想写
这样的东西state.get('field').get(state.get('order').get(nth))
在 reducer 附近以外的任何地方获取第 n 个字段,以便隐藏内部表示并且可以轻松更改。
我目前正在做的(这是一个小实验)是在与减速器相同的文件中添加一个函数:
schema.mapVisibleState = function (state) {
return {
getOrderedFields: () => state.get('order').map((fid) => state.get('fields').get(fid)
}
}
这用于相应的 React 组件(以及具有类似动机的 schema.bindActionCreators 函数):
export const Schema = connect(
(state) => schema.mapVisibleState (state),
(dispatch) => schema.bindActionCreators (dispatch)
)(_Schema) ;
现在,我可以按正确的顺序访问组件内部的字段,就像 this.props.getOrderedFields() 一样,而不用担心底层表示。它还具有额外的优点 属性,即可以轻松地对 getOrderedFields() 函数进行依赖注入。
此外,由于我对字段状态有类似的结构,我可以扩展它:
schema.mapVisibleState = function (state) {
return {
getOrderedFields: () => state.get('order').map((fid) => field.mapVisibleState(state.get('fields').get(fid)),
getNthField (nth) => field.mapVisibleState(state.get('fields').get(state.get('order').get(nth)))
}
}