Правильный способ обновления состояния в редукторах redux
Я новичок в синтаксисе redux и es6. Я делаю свое приложение с официальным учебником по сокращению, и с этим example.
Ниже приведен фрагмент JS. Моя задача - определить REQUEST_POST_BODY и RECEIVE_POST_BODY случаи в реестре сообщений.
Главная трудность - найти и обновить правильный объект в магазине.
Я пытаюсь использовать код из примера:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
Но он использовал простой массив сообщений. Не нужно было искать правильный пост по id.
Здесь мой код:
const initialState = {
items: [{id:3, title: '1984', isFetching:false}, {id:6, title: 'Mouse', isFetching:false}]
}
// Reducer for posts store
export default function posts(state = initialState, action) {
switch (action.type) {
case REQUEST_POST_BODY:
// here I need to set post.isFetching => true
case RECEIVE_POST_BODY:
// here I need to set post.isFetching => false and post.body => action.body
default:
return state;
}
}
function requestPostBody(id) {
return {
type: REQUEST_POST_BODY,
id
};
}
function receivePostBody(id, body_from_server) {
return {
type: RECEIVE_POST_BODY,
id,
body: body_from_server
};
}
dispatch(requestPostBody(3));
dispatch(receivePostBody(3, {id:3, body: 'blablabla'}));
Ответы
Ответ 1
С массивами
Если вы предпочитаете придерживаться массивов, то вы можете написать редуктор, который только Снасти одиночных post
объектов.
export default function reducePost(post, action) {
if(post.id !== action.id) return post;
switch(action.type) {
case REQUEST_POST_BODY:
return Object.assign({}, post, { isFetching: true });
case RECEIVE_POST_BODY:
return Object.assign({}, post, { isFetching: false, body: action.body });
default:
return post;
}
Ваш корневой редуктор станет:
export default function posts(state = initialState, action) {
return state.map(post => reducePost(post, action);
}
Мы просто запускаем наш новый редуктор для каждого поста в списке, чтобы вернуть обновленный массив постов. В этом случае уникальный идентификатор гарантирует, что будет изменен только один элемент.
С объектами
Если у каждого элемента есть уникальный идентификатор строки/числа, вы можете перевернуть массив и использовать вместо него object
.
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
};
}
Тогда вы можете упростить ваш редуктор.
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], { isFetching: true })
});
case RECEIVE_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], {
isFetching: false,
body: action.body
})
});
default:
return state;
}
Если вы счастливы поэкспериментировать с некоторым синтаксисом ES7, вы можете включить оператор распространения объектов с помощью Babel и переписать вызовы Object.assign
.
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return {
...state,
[id]: {...state[id], isFetching: true }
};
case RECEIVE_POST_BODY:
let id = action.id;
return {
...state,
[id]: {
...state[id],
isFetching: false,
body: action.body
}
};
default:
return state;
}
Если вы не очень заинтересованы в использовании синтаксиса распространения, тогда все еще возможно сделать Object.assign
более привлекательным.
function $set(...objects) {
return Object.assign({}, ...objects);
}
case RECEIVE_POST_BODY:
let id = action.id;
return $set(state, {
[id]: $set(state[id], {
isFetching: false,
body: action.body
})
});
Ответ 2
Если я правильно понимаю, у вас возникли проблемы с получением конкретной должности, которую вы хотите.
Прежде всего, если ваш редуктор также обновляет массив и объект в нем, он затрудняет чтение и обслуживание. Я предлагаю вам посмотреть короткое видео , объясняющее состав редуктора с массивами.
Вы можете упростить свой код, используя описанную там технику.
В вашем случае вы бы использовали редуктор posts
и редуктор post
, а редуктор posts
вызывал редуктор post
.
Что касается поиска правильного объекта для работы, предложение Дэна Принца облегчает его.
Наличие карты объектов вместо массива облегчит вам задачу. Соответствующий фрагмент кода из ответа Дэна:
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
];
}
Ответ 3
Я в значительной степени реализовал редукторы объектов с помощью Object.assign
, который работает, но по мере роста нашего проекта и добавления ряда зависимых компонентов он стал очень неэффективным, а рендеринг очень медленным.
Если бы я знал о immer, я бы использовал это с самого начала.
По сути, вы используете immer следующим образом, где в примере есть объект layers
, который выглядит следующим образом:
const initialState = {
layers: {
'layer1': { selected: false },
'layer2': { selected: true }
}
}
Reducers.js (извлечение)
import produce from 'immer'
import has from 'lodash/has'
export const layers = (state = null, action) => {
switch (action.type) {
case ADD_LAYER:
// Using 'immer.produce' only for consistency
// clearly the whole object changes in this case.
return produce(state, layers => {
// Take a copy of the prebuilt layer
var layer = Object.assign({}, action.layer)
// Add some defaults
if (!has(layer, 'selected')) {
layer.selected = false
}
layers[action.id] = layer
})
case SELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = true
})
case UNSELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = false
})
default:
return state
}
}
Actions.js (выдержка)
export const addLayer = id => ({
type: ADD_LAYER,
id
})
export const selectLayer = id => ({
type: SELECT_LAYER,
id
})
export const unSelectLayer = id => ({
type: UNSELECT_LAYER,
id
})
Ссылки:
https://github.com/immerjs/immer
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns