Управление состоянием Angular2 и правильное использование @ ngrx / store

Я пишу редукторы для своего приложения с помощью ngrx / store.

Вот схема состояния моего приложения:

{
    project: {
        name: string
        tasks: Array<Task>
    }
}

with:

interface Task {
    name: string
}

Пытаюсь писать чистые редукторы отдельными файлами.

Вот решение, которое я сейчас использую:

project.reducer.ts

import {tasksReducer} from './tasks.reducer';

const projectReducer = (state:Project = null, action: Action): Project => {
    switch (action.type) {
        case 'CREATE_PROJECT':
            return {
                name :'New project',
                tasks: []
            };
    };

    state.tasks = tasksReducer( state.tasks, action );

    return state;
}

tasks.reducer.ts

export const tasksReducer = (state:Array<Task> = [], action: Action): Array<Task> => {
    switch (action.type) {
        case 'ADD_TASK':
            return [...state, { name: 'New task' ];
        default:
            return state;
    };
}

Магазин предоставляется с использованием:

StoreModule.provideStore( combineReducers([{
    project: projectReducer
}]) );

Если я хочу добавить в свой проект другие поля, например поле тегов:

{
    project: {
        name: string,
        tasks: Array<Task>,
        tags: Array<Tags>
    }
}

Я могу создать отдельный tags.reducer.ts и использовать тот же подход для создания соответствующего редуктора.

Так что же плохого в таком подходе?

Я почти уверен, что у меня проблемы с неизменяемостью состояния моего приложения.

Пример:

  • Я отправляю действие CREATE_PROJECT, получаю новое состояние, и все в порядке.
  • Then i dispatch a ADD_TASK action.
    • The tasksReducer itself return a brand new array of tasks, BUT the main application state is mutated ... And that is not good !

Как вы считаете, как лучше всего решить эту проблему?

В более общем смысле:

Поскольку мой объект проекта будет становиться все больше и больше с большим количеством полей, как я могу:

  • Держите редуктор отдельно
  • Заставить редуктор работать на изолированной части «основного» редуктора.
  • Держите мое состояние неизменным

Буду рад поделиться хоть и мнениями по этому поводу!


person Clement    schedule 20.10.2016    source источник


Ответы (1)


Прежде всего несколько замечаний по поводу вашего projectReducer. В вашем операторе switch отсутствует регистр по умолчанию, который очень важен. (см. http://blog.kwintenp.com/how-to-write-clean-reducers-and-test-them/#defaultcase). Итак, добавьте это к этому редуктору:

default:
   return state;

Во-вторых, вы делаете что-то странное при вызове своего taskReducer. Когда вызывается projectReducer, вы всегда вызываете taskReducer. Когда на самом деле вы хотите вызвать taskReducer тогда и только тогда, когда это действие, с которым он может что-то сделать:

case 'ADD_TASK':
   // call it here

Вы также переопределяете текущее состояние его свойством задачи. Как вы сказали, вы изменяете свое состояние, чего следует избегать. Вы можете использовать оператор Object.assign, чтобы выполнить то, что вы хотите, например (полное исправление):

const projectReducer = (state:Project = null, action: Action): Project => {
    switch (action.type) {
        case 'CREATE_PROJECT':
            return {
                name :'New project',
                tasks: []
            };
        case 'ADD_TASK':
            return Object.assign({}, state, {tasks: tasksReducer( state.tasks, action )};
        default:
            return state;
    };
}

Теперь вы создаете новый объект Project, когда что-то меняете в задачах, чего вы и хотите.

Я бы порекомендовал вам прочитать весь мой пост в блоге, чтобы получить некоторые советы о том, как писать редукторы чистым способом.

На ваши общие вопросы можно было бы дать развернутый ответ, но я думаю, это упускает из виду. Несколько коротких ответов ниже.

Держите редуктор отдельно

-> Вы используете вспомогательный метод combReducers, который позволяет разделить редукторы.

Заставить редуктор работать на изолированной части «основного» редуктора.

-> То же, что и выше

Держите мое состояние неизменным

-> Следуйте советам в моем блоге и при необходимости используйте Object.assign и оператор распространения.

person KwintenP    schedule 25.10.2016
comment
Я согласен с вашим предложением, но у меня есть одно главное возражение против такого подхода: разделение проблем. ProjectReducer ДОЛЖЕН быть в курсе всех типов действий, реализуемых другими «подчиненными редукторами». Пример: здесь projectReducer должен знать о действии ADD_TASK. Для меня это звучит проблематично: по мере роста проекта это будет подвержено ошибкам: для каждого нового действия, добавляемого к вспомогательному редуктору, мы должны думать об изменении корневого (или родительского) редуктора, чтобы завершить отсутствующий оператор case. Я хочу, чтобы вспомогательный редуктор был самодостаточным - person Clement; 27.10.2016