Redux和组件架构

在上面的例子中,我们的counter 组件是一个智能组件。 它知道Redux,state的结构和它需要调用的动作。 理论上,你可以把这个组件放到你的应用程序的任何区域,只是让它工作。 但它将紧紧绑定到那个特定的状态切片和那些具体的动作。 例如,如果我们想要多个计数器跟踪页面上的不同内容怎么办? 例如,计算红色点击次数与蓝色点击次数。

为了帮助组件更通用和可重用,值得尝试将它们分成容器组件和演示组件。

容器组件 演示组件
Location Top level, route handlers Middle and leaf components
Aware of Redux Yes No
To read data Subscribe to Redux state Read state from @Input properties
To change data Dispatch Redux actions Invoke callbacks from @Output properties

redux docs.

记住这一点,让我们重构我们的 counter 是一个演示组件。 首先,让我们修改我们的app-container,在它上面有两个计数器组件,就像我们现在有的一样。

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'simple-redux'
  4. template: `
  5. <div>
  6. <h1>Redux: Two components, one state.</h1>
  7. <div style="float: left; border: 1px solid red;">
  8. <h2>Click Counter</h2>
  9. <counter></counter>
  10. </div>
  11. <div style="float: left; border: 1px solid blue;">
  12. <h2>Curse Counter</h2>
  13. <counter></counter>
  14. </div>
  15. </div>
  16. `
  17. })
  18. export class SimpleRedux {}

View Example

如您在示例中可以看到的,当单击按钮时,两个组件中的数字将同步更新。 这是因为计数器组件耦合到特定的状态和动作。

看看这个例子,你可以看到已经有一个 app/reducers/curse-reducer.tsapp/actions-curse-actions.ts。 它们几乎与计数器动作和计数器reducer相同,我们只是想创建一个新的reducer来保持它的状态。

要将计数器组件从智能组件转换为哑组件,我们需要更改它以将数据和回调传递到其中。 为此,我们将使用@Inputproperties将数据传递到组件中,并将操作回调作为@Output属性。

我们现在有一个很好的可重用的演示组件,它不知道Redux或我们的应用程序状态。

app/components/counter-component.ts

  1. import { Component, Input, Output, EventEmitter } from '@angular/core';
  2. import { Observable } from 'rxjs';
  3. @Component({
  4. selector: 'counter',
  5. template: `
  6. <p>
  7. Clicked: {{ counter | async }} times
  8. <button (click)="increment.emit()">+</button>
  9. <button (click)="decrement.emit()">-</button>
  10. <button (click)="incrementIfOdd.emit()">Increment if odd</button>
  11. <button (click)="incrementAsync.emit()">Increment async</button>
  12. </p>
  13. `
  14. })
  15. export class Counter {
  16. @Input() counter: Observable<number>;
  17. @Output() increment = new EventEmitter<void>();
  18. @Output() decrement = new EventEmitter<void>();
  19. @Output() incrementIfOdd = new EventEmitter<void>();
  20. @Output() incrementAsync = new EventEmitter<void>();
  21. }

接下来,让我们修改主应用的容器,将这些输入和输出连接到模板。

@Component app/src/containers/app-containter.ts

  1. @Component({
  2. selector: 'simple-redux',
  3. providers: [ CounterActions, CurseActions ],
  4. template: `
  5. <div>
  6. <h1>Redux: Presentational Counters</h1>
  7. <div style="float: left; border: 1px solid red;">
  8. <h2>Click Counter</h2>
  9. <counter [counter]="counter$"
  10. (increment)="counterActions.increment()"
  11. (decrement)="counterActions.decrement()"
  12. (incrementIfOdd)="counterActions.incrementIfOdd()"
  13. (incrementAsync)="counterActions.incrementAsync()">
  14. </counter>
  15. </div>
  16. <div style="float: left; border: 1px solid blue;">
  17. <h2>Curse Counter</h2>
  18. <counter [counter]="curse$"
  19. (increment)="curseActions.castCurse()"
  20. (decrement)="curseActions.removeCurse()"
  21. (incrementIfOdd)="curseActions.castIfOdd()"
  22. (incrementAsync)="curseActions.castAsync()">
  23. </counter>
  24. </div>
  25. </div>
  26. `
  27. })

此时,模板正在尝试调用我们的两个ActionCreatorServices,CounterActionsCurseActions上的操作; 我们只需要使用依赖注入来住它们:

app/src/containers/app-container.ts

  1. import { Component, View, Inject, OnDestroy, OnInit } from '@angular/core';
  2. import { select } from 'ng2-redux';
  3. import { Observable } from 'rxjs';
  4. import { CounterActions } from '../actions/counter-actions';
  5. import { CurseActions } from '../actions/curse-actions';
  6. @Component({ /* see above .... */})
  7. export class SimpleRedux {
  8. @select() counter$: Observable<number>;
  9. @select() curse$: Observable<number>;
  10. constructor(
  11. public counterActions: CounterActions,
  12. public curseActions: CurseActions) {
  13. }
  14. }

View Ng2-Redux Example View Ngrx Example

我们的两个Observablecounter$curse$ ,现在将在每次相关store属性由系统的其余部分更新时使用新值更新。