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

#angular #store #ngrx

#angular #Магазин #ngrx

Вопрос:

Я пишу редукторы для своего приложения, используя 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>
    }
}
  

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

Так что же не так с этим подходом?

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

Exemple:

  • Я отправляю действие CREATE_PROJECT, получаю новое состояние, и все в порядке.
  • Затем я отправляю действие ADD_TASK.
    • Сам tasksReducer возвращает совершенно новый набор задач, НО основное состояние приложения изменяется… И это нехорошо!

По вашему мнению, какой наилучший подход к решению этой проблемы?

В более общем плане:

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

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

Я был бы рад поделиться информацией и мнениями по этому поводу!

Ответ №1:

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

 default:
   return state;
  

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

 case 'ADD_TASK':
   // call it here
  

Вы также переопределяете текущее свойство state для его задачи. Как вы сказали, вы изменяете свое состояние, чего определенно следует избегать. Вы можете использовать оператор 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, когда вы что-то меняете в задачах, что вы и хотите.

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

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

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

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

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

—> То же, что указано выше

Сохраняйте мое состояние неизменным

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

Комментарии:

1. Я согласен с вашим предложением, но у меня есть одно главное возражение против такого подхода: разделение проблем . Разработчик проекта ДОЛЖЕН быть осведомлен о каждом типе действия, реализованном другими «вспомогательными редукторами». Пример: здесь projectReducer должен быть осведомлен о действии ADD_TASK. Для меня это звучит проблематично: по мере роста проекта это будет приводить к ошибкам: для каждого нового действия, добавляемого в вспомогательный редуктор, мы должны думать об изменении корневого (или родительского) редуктора, чтобы завершить отсутствующую инструкцию case. я бы хотел, чтобы вспомогательный редуктор был самодостаточным