Я переношу кодовую базу с ванильного Redux на Redux Toolkit. Я пытаюсь найти хороший способ вложить редукторы, созданные с помощью createReducer
, только для одного свойства.
Допустим, у меня есть установка, подобная следующему надуманному примеру, с вложенным user
редуктором и вложенным friends
редуктором. Пользователь может изменить свое имя, которое влияет только на себя, а также добавлять и удалять своих друзей, что влияет на него самого и его свойство массива friends
, которым управляет редуктор friends
.
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
. - Редуктор
friends
теперь имеет два способа изменения состояния: изменение состояния путем нажатия на него с помощью.push
и возврата нового пустого состояния путем прямого возврата[]
.
По моей интуиции, это сработает, поскольку это похоже на ту же логику. Однако это работает только для действия ADD_FRIEND
и ничего не делает или выдает ошибку об одновременном изменении состояния и возврате нового состояния для типа действия REMOVE_ALL_FRIENDS
.
Похоже, это связано с тем, что модифицируемое состояние превращает его в объект ImmerJS Proxy
в редукторе user
, но когда он передается редуктору friends
, он возвращает объект состояния напрямую вместо того, чтобы изменять его, заставляя RTK выдавать ошибку, как он говорит вы должны изменить только состояние возврата или, но не то и другое одновременно. В обработчике для ADD_FRIEND
это не проблема, поскольку он всегда изменяет состояние, как и все обработчики в user
.
В качестве обходного пути я вручную проверил, возвращает ли friends
редуктор Proxy
или новое состояние напрямую, и если он возвращает новое состояние, он устанавливает его в редукторе user
, но я уверен, что есть способ лучше:
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;
}
};
});
Тогда у меня вопрос, как я могу это исправить, чтобы мне не приходилось вручную проверять тип возвращаемого значения и можно было легко вкладывать редукторы RTK друг в друга, будь то реструктуризация моих редукторов или исправление логики кода?
В идеале я бы по-прежнему хотел, чтобы редуктор friends
был вложен в редуктор user
, так как именно так много ванильного кода Redux структурируют свою логику состояния с множеством разных редукторов, обрабатывающих много разных частей состояния, вместо того, чтобы все они были вложены в корень. уровень с одним вызовом combineReducers
, но если есть лучшее и более чистое решение, я тоже с этим согласен.
Спасибо за любую помощь и извините за длинный пост - просто хотел быть как можно более подробным, поскольку другие онлайн-решения, похоже, не решают эту точную проблему.
friends
в развернутом объекте, а затем, когдаfriends
изменил состояние, ImmerJS выдал ошибку, поскольку я возвращал состояние изuser
reducer, а также одновременно модифицируя его изfriends
reducer. - person Matthew W.   schedule 27.02.2021