Я заметил, что почти все утки в моем проекте используют одинаковую структуру действий, редукторов, селекторов и т. Д.
Я никогда не реализовывал структуру reducks в Redux, но однажды я обнаружил, что при управлении объектами моего домена (например, персонами, заказами, продуктами и т. д.) я генерирую идентичных действий, редукторов и т. д.
Например, мне всегда казалось, что меня волнует:
- Мы в данный момент выбираем объект?
isFetching
- Были ли какие-либо ошибки при получении объекта?
error
- Каковы фактические данные сущности?
data
- Когда был последний раз выбран объект?
lastUpdated
Кроме того, доменные объекты постоянно добавляются, поэтому непрерывное копирование и вставка редуктора / действий не идеальны.Нам нужен способ динамически хранения данных в Redux, и мы хотим, чтобы данные всегда были присоединены к таким свойствам, как isFetching
и lastUpdated
.
{
"entities": {
<SOME_ENTITY>: {
"isFetching" : null // Am I fetching?
"lastUpdated": null // When was I last fetched?
"data" : null // Here's my data!
"error" : null // Error during fetching
}
}
}
Так что, если мы сгенерировали действие со строковым литералом, который будет использоваться в качестве ключа в Redux (например, products
, orders
)?Таким образом, мы можем выдавать любые доступные типы действий (FETCH_REQUEST
и т. Д.), И нам просто нужно обновить ключ entity
, который автоматически выделит нам пространство в Магазине:
dispatch({
entity : "products",
type : "FETCH_SUCCESS",
data : [{id: 1}],
lastUpdated: Date.now()
});
dispatch({
entity : "orders",
type : "FETCH_SUCCESS",
data : [{id: 2}, {id: 3}],
lastUpdated: Date.now()
});
Результирующее состояние
{
"entities": {
"products": {
"isFetching" : false,
"lastUpdated": 1526746314736,
"data" : [{id: 1}]
"error" : null
},
"orders": {
"isFetching" : false,
"lastUpdated": 1526746314943,
"data" : [{id: 2}, {id: 3}]
"error" : null
}
}
}
Универсальный редуктор сущностей
function entities (state = {}, action) {
switch (action.type) {
case FETCH_SUCCESS: // fall through
case FETCH_FAILURE: // fall through
case FETCH_REQUEST: {
return Object.assign({}, state, {
[action.entity]: entity(
state[action.entity],
action
)
});
}
default: {
return state;
}
}
};
Редуктор сущностей
const INITIAL_ENTITY_STATE = {
isFetching : false,
lastUpdated: null,
data : null,
error : null
};
function entity (state = INITIAL_ENTITY_STATE, action) {
switch (action.type) {
case FETCH_REQUEST: {
return Object.assign({}, state, {
isFetching: true,
error : null
});
}
case FETCH_SUCCESS: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : action.data,
error : null
});
}
case FETCH_FAILURE: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : null,
error : action.error
});
}
}
}
Опять же, используя универсальный редуктор, мы можем динамически сохранять все, что захотим, в Redux, поскольку мы используем строку entity
ниже в качестве ключа в Redux
dispatch({type: "FETCH_REQUEST", entity: "foo"});
dispatch({type: "FETCH_REQUEST", entity: "bar"});
dispatch({type: "FETCH_REQUEST", entity: "baz"});
Результирующее состояние
{
"entities": {
"foo": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"bar": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"baz": {
"isFetching": false,
"error": null,
"lastUpdated": null,
"data": null
}
}
}
Если это выглядит интересно, я написал небольшую библиотеку (плагин!), Которая в точности соответствует описанному выше:
Демонстрация в реальном времени: http://mikechabot.github.io/react-boilerplate/dist/
Тем не менее, я не выдвигаю эту библиотеку, я простопытаясь описать подход, который я выбрал, учитывая мою проблему.Ваш набор действий может быть совершенно другим, и в этом случае вы все еще можете реализовать общий шаблон, но очевидно, что редуктор будет вести себя по-другому.