ngrx / redux操作上下文的最佳实践

时间:2017-12-27 14:39:05

标签: javascript angular typescript redux ngrx

我尝试找到这种情况的最佳做法,但我找不到。

问题: 我不想重复动作文件,就像在我的例子中一样 home-todos.actions和sport-todos-actions,我想使用相同的to-dos.action文件。和同样的减速器。

示例: 我编写了一个todo应用程序,例如,在这个例子中你可以看到问题,如果我发送一个类型为'ADD_TODO_ASYNC'的动作它将在家中发送(效果和减速器)和运动(效果和减速器)

todos.actions.ts

const ADD_TODO_ASYNC = 'ADD TODO ASYNC';
const ADD_TODO_COMPLETE = 'ADD TODO COMPLETE';
const ADD_TODO_FAILD = 'AD TODO FAILD';

class addTodoComplete {
    type = ADD_TODO_COMPLETE;
}
class addTodoFaild {
    type = ADD_TODO_COMPLETE;
}

export type Actions = addTodoComplete | addTodoFaild;

sport.effects.ts

@Injectable()
export class SportTodosService {

    @Effect() ADD_TODO_ASYNC$ = this.actions$.ofType(TodosActionTypes.ADD_TODO_ASYNC)
    .map(toPayload)
    .swithMap( (todo: Todo) => this.api.addTodo(todo))
    .map((todo: Todo) => TodosActionTypes.addTodoComplete(todo))
    constructor(
        private actions$: Actions,
        private api: api
    ) { }

}

home.effects.ts

export class HomeTodosService {
    @Effect() ADD_TODO_ASYNC$ = this.actions$.ofType(TodosActionTypes.ADD_TODO_ASYNC)
        ...
    constructor(
        ...
    ) { }

}

减速器

function todosReducer(state, action: TodosActionTypes.Actions) {
    switch (action.type) {
        case TodosActionTypes.ADD_TODO_COMPLETE:
            return state;
        default:
            return state;
    }
}

app.module.ts

@NgModule({
    declarations: [
      AppComponent
    ],
    imports: [
      StoreModule.forRoot({
        sport: todosReducer,
        home: todosReducer,
      }),
      EffectsModule.forRoot([
        SportTodosService
        HomeTodosService,
      ])
    ],
    providers: [
        api
    ],
    bootstrap: [AppComponent]
  })
  export class AppModule { }

我试着了解这种情况的最佳做法是什么? 使用'HOME_ADD_TODO'和& 'SPORT_ADD_TODO'?

还是有官方的方式?

如果您知道解决方案,请不要关心解决方案是针对redux还是ngrx

感谢所有

3 个答案:

答案 0 :(得分:1)

Here是与ngrx的一些模式和实践的链接。

它的目的是按照您描述的方式工作。 this.actions$是一个Observable,所以它会在你使用它的任何地方发出。由于TodosActionTypes.ADD_TODO_ASYNChome.effects.ts中的sport.effects.ts类型相同,因此它会在两个地方都发出。

我不确定您是否可以避免在您的情况下采取单独的操作,但您可以减少样板代码的数量。

我会尝试这样的事情:

todos.actions.ts

abstract class addTodoComplete{
   constructor(readonly type: string){
      //rest of the behavior
   }
}
abstract class addTodoFailed{
   constructor(readonly type: string){
     //rest of the behavior
   }
}

todos.sport-actions.ts

const ADD_TODO = "[Sport] Add Todo";
const ADD_TODO_FAILED = "[Sport] Add Todo Failed";
class sportsAddTodoComplete extends addTodoComplete{
   constructor(){
      super(ADD_TODO);
      //rest of the behavior
   }
}
class sportsAddTodoFailed extends addTodoFailed{
   constructor(){
     super(ADD_TODO_FAILED);
      //rest of the behavior
   }
}

同样适用于家庭版。

此外,您可能会分开SportTodosActionTypesHomeTodosActionTypes

你不会完全从" copy-paste"中拯救自己,但它应该在某种程度上有所帮助。

修改

至于减速器,确实,通过这种方法,你必须编写两个减速器,但它不必是一个"复制 - 粘贴"工作

sport.reducer.ts

import { todoReducer } from './reducer';

export function sportsTodoReducer(state, action: SportTodoActionTypes.Actions){
   todoReducer(state, action);
}

类似于home版本。

答案 1 :(得分:1)

要了解此问题,您需要再次考虑您的应用架构。 通常,可重复使用的减速器/动作是不正确的。

为什么不正确?在目前的观点中,编写可重复使用的减速器和动作,较少的样板,而不是" DRY"似乎很棒。在你的应用程序的示例中。 ' ADD_TO_DO'对于家庭和运动来说是平等的。

但将来会很危险,认为你的老板/客户需要体育运动的未来add_to_do。如果您更改可重复使用的减速器中的逻辑。你的应用程序将破产。 (您可以使用if语句开始修补可重复使用的reducer以使其正常工作,但如果您的应用程序增长,则不灵活/可读/维护)。

所以是的,似乎你需要在这种情况下写2个减速器和2个动作文件。在目前它填补相同,但在未来它将是优势和flexibale。

祝你好运!

答案 2 :(得分:0)

这些情况下的解决方案是使用操作命名空间

操作常量充当操作的唯一标识符。由于应用程序中可能存在与商店的不同切片相对应的许多操作,因此我们可以通过使用操作命名空间的概念来保​​护我们的商店免于重复操作逻辑失败。看看这个:

// todos.actions.ts
export const ADD_TODO = '[Home] Add Todo';

我们只是将一个命名空间附加到Action Constant,理想情况下,它对应于我们正在使用的商店切片的名称 - 通常是您当前正在处理的功能模块的名称。

如果我们发现自己通过记录操作调试应用程序,这个命名空间将清楚地说明我们正在排除故障的存储切片和操作上下文,因为我们会看到类似这样的事情(想象我们将视图从“Home”切换到“运动”):

[Home] Add Todo
[Home] Add Todo Success
[Sport] Add Todo
[Sport] Add Todo Success

查看SOURCE更多详情