如何为单个 属性 嵌套 Redux Toolkit reducer
How to nest Redux Toolkit reducers for a single property
我正在将代码库从 vanilla Redux 迁移到 Redux Toolkit。我正在尝试找到一种很好的方法来嵌套使用 createReducer
创建的 reducer 仅用于单个 属性.
假设我有一个类似于以下人为示例的设置,其中包含一个 user
reducer 和一个嵌套在其下的 friends
reducer。用户可以更改他们的名字,这只会影响他自己,也可以添加和删除他们的朋友,这会影响他自己和它的 friends
数组 属性,它由 friends
reducer 管理。
const CHANGE_NAME = "CHANGE_NAME";
const ADD_FRIEND = "ADD_FRIEND";
const REMOVE_ALL_FRIENDS = "REMOVE_ALL_FRIENDS";
const initialState = {
username: "",
email: "",
lastActivity: 0,
friends: [],
};
const user = (state = initialState, action) => {
switch (action.type) {
case CHANGE_NAME: {
const { newName, time } = action.payload;
return {
...state,
name: newName,
lastActivity: time,
};
}
case ADD_FRIEND:
case REMOVE_ALL_FRIENDS: {
const { time } = action.payload;
return {
...state,
friends: friends(state.friends, action),
lastActivity: time,
};
}
default: {
return {
...state,
friends: friends(state.friends, action),
};
}
}
};
const friends = (state = initialState.friends, action) => {
switch (action.type) {
case ADD_FRIEND: {
const { newFriend } = action.payload;
return [...state, newFriend];
}
case REMOVE_ALL_FRIENDS: {
return [];
}
default: {
return state;
}
}
};
注意事项:
friends
必然与 user
相关,所以我决定将它嵌套在它的状态片中。
user
手动调用 friends
reducer 来计算 friends
可能具有重叠动作类型的状态切片,并且仅针对那个 friends
属性.
我现在正尝试使用 Redux Toolkit createReducer
s 重构它。我的第一次尝试如下:
import { createReducer } from "@reduxjs/toolkit";
const CHANGE_NAME = "CHANGE_NAME";
const ADD_FRIEND = "ADD_FRIEND";
const REMOVE_ALL_FRIENDS = "REMOVE_ALL_FRIENDS";
const initialState = {
username: "",
email: "",
lastActivity: 0,
friends: [],
};
const user = createReducer(initialState, (builder) => {
builder
.addCase(CHANGE_NAME, (state, action) => {
const { newName, time } = action.payload;
state.name = newName;
state.lastActivity = time;
})
.addMatcher((action) => action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS),
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
state.friends = friends(state.friends, action);
};
});
const friends = createReducer(initialState, (builder) => {
builder
.addCase(ADD_FRIEND, (state, action) => {
const { newFriend } = action.payload;
state.push(newFriend);
})
.addCase(REMOVE_ALL_FRIENDS, () => []);
});
注意事项:
user
reducer 的最后一个匹配器是这里的主要焦点。
friends
reducer 现在有两种修改状态的方法:通过使用 .push
推送到它来“修改”状态,以及 return 直接通过新的空状态returning []
.
根据我的直觉,这会起作用,因为它看起来是相同的逻辑。但是,这仅适用于 ADD_FRIEND
操作,并且不执行任何操作或发出有关同时修改状态和 return 为 REMOVE_ALL_FRIENDS
操作类型创建新状态的错误。
这似乎是因为被修改的状态将它变成 user
reducer 中的 ImmerJS Proxy
对象,但是当它被传递到 friends
reducer 并且它 return 是一个状态对象,而不是直接修改它导致 RTK 抛出错误,因为它说您只能修改 或 return 状态,但不能同时修改两者。在 ADD_FRIEND
的处理程序中,这不是问题,因为它总是修改状态,与 user
.
中的所有处理程序一样
作为一个 hacky 解决方法,我手动检查了 friends
reducer return 是一个 Proxy
还是一个新状态,如果它 return 是一个新状态,那么它在 user
reducer 中设置它,但我相信有更好的方法:
import { createReducer, current } from "@reduxjs/toolkit";
const user = createReducer(initialState, (builder) => {
builder
.addMatcher((action) => action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS),
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
const result = friends(state.friends, action);
let output;
// If state has been returned directly this will error and we can set the state manually,
// Else this will not error because a Proxy has been returned, and thus the state has been
// set already by the sub-reducer.
try {
output = current(result);
} catch (error) {
output = result;
}
if (output) {
state.progress = output;
}
};
});
我的问题是如何解决这个问题,这样我就不必手动检查 return 类型,并且可以轻松地将 RTK reducers 嵌套在彼此之间,无论是通过重组我的 reducers 还是修复代码逻辑?
理想情况下,我仍然希望将 friends
reducer 嵌套在 user
reducer 下,因为很多“vanilla”Redux 代码都是这样构造它们的状态逻辑的,许多不同的 reducer 处理许多不同的状态,而不是通过单个 combineReducers
调用将它们全部嵌套在根级别,但如果有更好更清洁的解决方案,我也很好。
感谢您的帮助,很抱歉 post - 只是想尽可能详细,因为其他在线解决方案似乎没有解决这个确切的问题。
这实际上可能是 Redux Toolkit 中的错误。您能否在 github 问题跟踪器上提交一个复制 CodeSandbox 的问题?
在这种特殊情况下,很容易处理 user
reducer 中的 friends
属性:state.friends.push(newFriend)
或 state.friends = []
。但是将它分开应该没有任何问题。
我在尝试 运行 您的代码时确实注意到了一些问题:
- 使用整个用户的
initialState
作为friends
的初始状态,而不是initialState.friends
或[]
addMatcher
中围绕 action.type
检查 的括号不匹配
- 分配给
state.name
而不是 state.username
修复这些问题后,我无法重现您的问题。我可以成功添加和删除朋友。
问题是我原来的 user
reducer 代码是 reducer 通过传播状态并在该对象传播中设置 friends
属性 来返回一个新的状态对象。这从 ImmerJS 中产生了一个错误,因为它从 user
reducer 返回一个新值,同时也在 friends
reducer 中修改它。
我发布的代码有效(由于 Linda 做了一些修改),但为了修复我的原始代码(我没有发布产生错误的版本 - 抱歉)我必须更改以下内容:
.addMatcher(
(action) =>
action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS,
(state, action) => ({
...state,
lastActivity: action.payload.time,
friends: friends(state.friends, action)
})
)
至:
.addMatcher(
(action) =>
action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS,
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
state.friends = friends(state.friends, action);
}
)
谢谢大家的帮助。
我正在将代码库从 vanilla Redux 迁移到 Redux Toolkit。我正在尝试找到一种很好的方法来嵌套使用 createReducer
创建的 reducer 仅用于单个 属性.
假设我有一个类似于以下人为示例的设置,其中包含一个 user
reducer 和一个嵌套在其下的 friends
reducer。用户可以更改他们的名字,这只会影响他自己,也可以添加和删除他们的朋友,这会影响他自己和它的 friends
数组 属性,它由 friends
reducer 管理。
const CHANGE_NAME = "CHANGE_NAME";
const ADD_FRIEND = "ADD_FRIEND";
const REMOVE_ALL_FRIENDS = "REMOVE_ALL_FRIENDS";
const initialState = {
username: "",
email: "",
lastActivity: 0,
friends: [],
};
const user = (state = initialState, action) => {
switch (action.type) {
case CHANGE_NAME: {
const { newName, time } = action.payload;
return {
...state,
name: newName,
lastActivity: time,
};
}
case ADD_FRIEND:
case REMOVE_ALL_FRIENDS: {
const { time } = action.payload;
return {
...state,
friends: friends(state.friends, action),
lastActivity: time,
};
}
default: {
return {
...state,
friends: friends(state.friends, action),
};
}
}
};
const friends = (state = initialState.friends, action) => {
switch (action.type) {
case ADD_FRIEND: {
const { newFriend } = action.payload;
return [...state, newFriend];
}
case REMOVE_ALL_FRIENDS: {
return [];
}
default: {
return state;
}
}
};
注意事项:
friends
必然与user
相关,所以我决定将它嵌套在它的状态片中。user
手动调用friends
reducer 来计算friends
可能具有重叠动作类型的状态切片,并且仅针对那个friends
属性.
我现在正尝试使用 Redux Toolkit createReducer
s 重构它。我的第一次尝试如下:
import { createReducer } from "@reduxjs/toolkit";
const CHANGE_NAME = "CHANGE_NAME";
const ADD_FRIEND = "ADD_FRIEND";
const REMOVE_ALL_FRIENDS = "REMOVE_ALL_FRIENDS";
const initialState = {
username: "",
email: "",
lastActivity: 0,
friends: [],
};
const user = createReducer(initialState, (builder) => {
builder
.addCase(CHANGE_NAME, (state, action) => {
const { newName, time } = action.payload;
state.name = newName;
state.lastActivity = time;
})
.addMatcher((action) => action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS),
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
state.friends = friends(state.friends, action);
};
});
const friends = createReducer(initialState, (builder) => {
builder
.addCase(ADD_FRIEND, (state, action) => {
const { newFriend } = action.payload;
state.push(newFriend);
})
.addCase(REMOVE_ALL_FRIENDS, () => []);
});
注意事项:
user
reducer 的最后一个匹配器是这里的主要焦点。friends
reducer 现在有两种修改状态的方法:通过使用.push
推送到它来“修改”状态,以及 return 直接通过新的空状态returning[]
.
根据我的直觉,这会起作用,因为它看起来是相同的逻辑。但是,这仅适用于 ADD_FRIEND
操作,并且不执行任何操作或发出有关同时修改状态和 return 为 REMOVE_ALL_FRIENDS
操作类型创建新状态的错误。
这似乎是因为被修改的状态将它变成 user
reducer 中的 ImmerJS Proxy
对象,但是当它被传递到 friends
reducer 并且它 return 是一个状态对象,而不是直接修改它导致 RTK 抛出错误,因为它说您只能修改 或 return 状态,但不能同时修改两者。在 ADD_FRIEND
的处理程序中,这不是问题,因为它总是修改状态,与 user
.
作为一个 hacky 解决方法,我手动检查了 friends
reducer return 是一个 Proxy
还是一个新状态,如果它 return 是一个新状态,那么它在 user
reducer 中设置它,但我相信有更好的方法:
import { createReducer, current } from "@reduxjs/toolkit";
const user = createReducer(initialState, (builder) => {
builder
.addMatcher((action) => action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS),
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
const result = friends(state.friends, action);
let output;
// If state has been returned directly this will error and we can set the state manually,
// Else this will not error because a Proxy has been returned, and thus the state has been
// set already by the sub-reducer.
try {
output = current(result);
} catch (error) {
output = result;
}
if (output) {
state.progress = output;
}
};
});
我的问题是如何解决这个问题,这样我就不必手动检查 return 类型,并且可以轻松地将 RTK reducers 嵌套在彼此之间,无论是通过重组我的 reducers 还是修复代码逻辑?
理想情况下,我仍然希望将 friends
reducer 嵌套在 user
reducer 下,因为很多“vanilla”Redux 代码都是这样构造它们的状态逻辑的,许多不同的 reducer 处理许多不同的状态,而不是通过单个 combineReducers
调用将它们全部嵌套在根级别,但如果有更好更清洁的解决方案,我也很好。
感谢您的帮助,很抱歉 post - 只是想尽可能详细,因为其他在线解决方案似乎没有解决这个确切的问题。
这实际上可能是 Redux Toolkit 中的错误。您能否在 github 问题跟踪器上提交一个复制 CodeSandbox 的问题?
在这种特殊情况下,很容易处理 user
reducer 中的 friends
属性:state.friends.push(newFriend)
或 state.friends = []
。但是将它分开应该没有任何问题。
我在尝试 运行 您的代码时确实注意到了一些问题:
- 使用整个用户的
initialState
作为friends
的初始状态,而不是initialState.friends
或[]
addMatcher
中围绕action.type
检查 的括号不匹配
- 分配给
state.name
而不是state.username
修复这些问题后,我无法重现您的问题。我可以成功添加和删除朋友。
问题是我原来的 user
reducer 代码是 reducer 通过传播状态并在该对象传播中设置 friends
属性 来返回一个新的状态对象。这从 ImmerJS 中产生了一个错误,因为它从 user
reducer 返回一个新值,同时也在 friends
reducer 中修改它。
我发布的代码有效(由于 Linda 做了一些修改),但为了修复我的原始代码(我没有发布产生错误的版本 - 抱歉)我必须更改以下内容:
.addMatcher(
(action) =>
action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS,
(state, action) => ({
...state,
lastActivity: action.payload.time,
friends: friends(state.friends, action)
})
)
至:
.addMatcher(
(action) =>
action.type === ADD_FRIEND || action.type === REMOVE_ALL_FRIENDS,
(state, action) => {
const { time } = action.payload;
state.lastActivity = time;
state.friends = friends(state.friends, action);
}
)
谢谢大家的帮助。