ngrx实例 - 宴会策划者

前端之家收集整理的这篇文章主要介绍了ngrx实例 - 宴会策划者前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

simple party planner
这是ngrx教程的一部分 原文在这里

这里是网上的一张redux的动态图 画的十分传神

我们将要建造的示例应用程序是一个简单的派对策划者。
用户应该能够输入参加者及其客人的列表,跟踪谁确认参加者,通过特定标准过滤与会者,并快速查看有关活动的重要统计信息。
源码在这里
最终完成界面如图:

增加按钮增加的应该团体(Person),是被邀请的(Invited),团体下面有Guests,是一共参与的人数,确定与会的为Attending,是按照Person算的

算状态

在做之前 应先确定一共有多少种状态 即–我们需要处理几种action
增加一个person
删除一个person
向person中添加guest
从person中删除guest
toggle Attending 是否确实参加单选

//src/actions.ts
//Person Action Constants 五种状态
export const ADD_PERSON = 'ADD_PERSON';
export const REMOVE_PERSON = 'REMOVE_PERSON';
export const ADD_GUEST = 'ADD_GUEST';
export const REMOVE_GUEST = 'REMOVE_GUEST';
export const TOGGLE_ATTENDING = 'TOGGLE_ATTENDING';

//Party Filter Constants 过滤器功能
export const SHOW_ATTENDING = 'SHOW_ATTENDING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_WITH_GUESTS = 'SHOW_GUESTS';
//src/people.ts
import {
  ADD_PERSON,REMOVE_PERSON,ADD_GUEST,REMOVE_GUEST,TOGGLE_ATTENDING
} from './actions';

const details = (state,action) => {
  switch(action.type){
    case ADD_GUEST:
      if(state.id === action.payload){
          return Object.assign({},state,{guests: state.guests + 1});
      }
      return state;
    case REMOVE_GUEST:
      if(state.id === action.payload){
          return Object.assign({},{guests: state.guests - 1});
        }
      return state;

    case TOGGLE_ATTENDING:
      if(state.id === action.payload){
          return Object.assign({},{attending: !state.attending});
      }
      return state;

    default:
      return state;
  }
}

//remember to avoid mutation within reducers
export const people = (state = [],action) => {
  switch(action.type){
    case ADD_PERSON:
      return [
        ...state,Object.assign({},{id: action.payload.id,name: action.payload.name,guests:0,attending: false})
      ];

    case REMOVE_PERSON:
      return state
        .filter(person => person.id !== action.payload);
     //为了缩短我们的代码长度,我们把细节交给details reducer   
    case ADD_GUEST:
      return state.map(person => details(person,action));

    case REMOVE_GUEST:
      return state.map(person => details(person,action));

    case TOGGLE_ATTENDING:
      return state.map(person => details(person,action));
    default:
      return state;
  }
}

代码分析:
- 创建了两个reducer,detail和people
- 从上面的 add person状态 我们可以看出 我们需要传递什么对象
{id: action.payload.id,name: action.payload.name,guests:0,attending: false}
如此说来,一个传入的数据对象结构为

interface Object {
    id:string,name:string,guests:number,attending:boolean
}

聪明组件 笨组件

聪明组件 或容器组件应该是根级别、可路由化的组件。这些组件通常可以直接访问存储或派生。智能组件通过服务或直接处理视图事件和操作的调度。智能组件还处理在同一视图内从子组件发出的事件的逻辑。
笨或子组件通常仅用于呈现,仅依靠@Input参数,以适当的方式对接收的数据进行操作。当相关事件发生在哑组件中时,它们被发出以由父聪明组件处理。笨组件将弥补您的大部分应用程序,因为它们应该是小型的,集中的和可重复使用的。

本程序需要一个聪明组件作为整体调度管理

@Component({
    selector: 'app',template: `
      <h3>@ngrx/store 宴会策划者</h3>
      <person-input
        (addPerson)="addPerson($event)"
      >
      </person-input>
      <person-list
        [people]="people"
        (addGuest)="addGuest($event)"
        (removeGuest)="removeGuest($event)"
        (removePerson)="removePerson($event)"
        (toggleAttending)="toggleAttending($event)"
      >
      </person-list>
    `,directives: [PersonList,PersonInput]
})
export class App {
    public people;
    private subscription;

    constructor(
     private _store: Store
    ){
      /* 演示使用没有异步管, 我们将在下一课中探索异步管道 */
      this.subscription = this._store
        .select('people')
        .subscribe(people => {
          this.people = people;
      });
    }
    //所有状态变化的动作都被调度到reducer处理
    addPerson(name){
      this._store.dispatch({type: ADD_PERSON,payload: {id: id(),name})
    }

    addGuest(id){
      this._store.dispatch({type: ADD_GUEST,payload: id});
    }

    removeGuest(id){
      this._store.dispatch({type: REMOVE_GUEST,payload: id});
    }

    removePerson(id){
      this._store.dispatch({type: REMOVE_PERSON,payload: id});
    }

    toggleAttending(id){
      this._store.dispatch({type: TOGGLE_ATTENDING,payload: id})
    }
    /* 如果您不使用异步管道并创建手动订阅 永远记得在ngOnDestroy取消订阅 */
    ngOnDestroy(){
      this.subscription.unsubscribe();
    }
}

笨组件 - PersonList

@Component({
    selector: 'person-list',template: `
      <ul>
        <li 
          *ngFor="let person of people"
          [class.attending]="person.attending"
        >
           {{person.name}} - Guests: {{person.guests}}
           <button (click)="addGuest.emit(person.id)">+</button>
           <button *ngIf="person.guests" (click)="removeGuest.emit(person.id)">-</button>
           Attending?
           <input type="checkBox" [(ngModel)]="person.attending" (change)="toggleAttending.emit(person.id)" />
           <button (click)="removePerson.emit(person.id)">Delete</button>
        </li>
      </ul>
    `
})
export class PersonList {
    /* 笨组件只能根据输入显示数据 发送相关事件返回父/容器组件来处理 */
    @Input() people;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}

利用AsyncPipe

AsyncPipe是一个独特的,有状态的管道,用于处理 Observables 和 Promises。当在具有Observables的模板表达式中使用AsyncPipe时,提供的Observable将被注册,并且您的视图中显示已发出的值。该管道还可以处理提供的可观察的取消订阅,从而节省了在ngOnDestroy中手动清理订阅的精神开销。在一个Store应用程序中,您将发现几乎在所有组件视图中都用到了AsyncPipe。

在我们的模板中使用AsyncPipe很容易。您可以通过异步管理任何可观察(或承诺),并创建订阅,更新源发射的模板值。因为我们正在使用AsyncPipe,我们还可以从组件构造函数删除手工订阅,并从ngOnDestroy生命周期钩子中取消订阅。现在我们在幕后处理。

用Async Pipe重构代码

@Component({
    selector: 'app',template: `
      <h3>@ngrx/store Party Planner</h3>
      <person-input
        (addPerson)="addPerson($event)"
      >
      </person-input>
      <person-list
        [people]="people | async"
        (addGuest)="addGuest($event)"
        (removeGuest)="removeGuest($event)"
        (removePerson)="removePerson($event)"
        (toggleAttending)="toggleAttending($event)"
      >
      </person-list>
    `,PersonInput]
})
export class App {
    public people;
    private subscription;

    constructor(
     private _store: Store
    ){
      /* people的Observable , 利用async pipe 它将在我们的模板中被订阅 新值将在我们的模板中出现。 取消订阅将在组件自动调用时被处置。 */
      this.people = _store.select('people');
    }
    //all state-changing actions get dispatched to and handled by reducers
    addPerson(name){
      this._store.dispatch({type: ADD_PERSON,payload: name})
    }

    addGuest(id){
      this._store.dispatch({type: ADD_GUEST,payload: id})
    }
    //ngOnDestroy to unsubscribe is no longer necessary
}

利用ChangeDetection.OnPush

利用angular中的集中式状态树不仅可以带来可预测性和可维护性,还可以提高性能。为了实现此性能优势,我们可以使用OnPush的changeDetectionStrategy。

OnPush背后的概念很简单,当组件仅依赖输入时,这些输入引用不会改变,Angular可以跳过组件树的该部分的运行更改检测。如前所述,所有state的委托应在智能或顶级组件中处理。这使我们的应用程序中的大多数组件完全依赖于输入,安全地允许我们在组件定义中将C​​hangeDetectionStrategy设置为OnPush。这些组件现在可以放弃变更检测,直到有必要,从而为我们提供自由的性能提升。

要在组件中使用OnPush更改检测,我们需要在@Component装饰器中将changeDetection属性设置为ChangeDetection.OnPush。而已!现在,Angular将忽略这些组件的笨组件和子项的更改检测,直到其输入引用有变化。
改写

@Component({
    selector: 'person-list',template: `
      <ul>
        <li 
          *ngFor="let person of people"
          [class.attending]="person.attending"
        >
           {{person.name}} - Guests: {{person.guests}}
           <button (click)="addGuest.emit(person.id)">+</button>
           <button *ngIf="person.guests" (click)="removeGuest.emit(person.id)">-</button>
           Attending?
           <input type="checkBox" [(ngModel)]="person.attending" (change)="toggleAttending.emit(person.id)" />
           <button (click)="removePerson.emit(person.id)">Delete</button>
        </li>
      </ul>
    `,changeDetection: ChangeDetectionStrategy.OnPush
})
/* 使用“onpush”变化检测,仅依靠的组件输入可以跳过更改检测,直到这些输入引用改变, 这可以提供显着的性能提升 */
export class PersonList {
    /* 笨组件只能根据输入显示数据 发送相关事件交由父/容器组件来处理 */
    @Input() people;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}

下拉筛选状态

大多数store应用会制作多个reducers,每一个负责它们自己的state,在这里例子中,我们有两个。一个管理与会人员,另一个用户此列表的当前活动过滤器。
老样子 我们先写action状态。
我们创建一个partyFilter reducer,我们有几个方法来创建,我们可以返回一个过滤器提供的字符串,但是根据当前的过滤器state返回应用于派对列表的function是更可扩展的。在将来,添加更多的过滤器就像创建一个新的case语句一样简单地返回相应的投影函数
过滤器reducer

import {
  SHOW_ATTENDING,SHOW_ALL,SHOW_WITH_GUESTS
} from './actions';

//根据所选过滤器返回适当的function
export const partyFilter = (state = person => person,action) => {
    switch(action.type){
        case SHOW_ATTENDING:
            return person => person.attending;
        case SHOW_ALL:
            return person => person;
        case SHOW_WITH_GUESTS:
            return person => person.guests;
        default:
            return state;
    }
};

Party Filter Actions

//Party Filter Constants
export const SHOW_ATTENDING = 'SHOW_ATTENDING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_WITH_GUESTS = 'SHOW_GUESTS';

Party Filter Select

import {Component,Output,EventEmitter} from "angular2/core";
import {
  SHOW_ATTENDING,SHOW_WITH_GUESTS
} from './actions';

@Component({
    selector: 'filter-select',template: `
      <div class="margin-bottom-10">
        <select #selectList (change)="updateFilter.emit(selectList.value)">
            <option *ngFor="let filter of filters" value="{{filter.action}}">
                {{filter.friendly}}
            </option>
        </select>
      </div>
    `
})
export class FilterSelect {
    public filters = [
        {friendly: "All",action: SHOW_ALL},{friendly: "Attending",action: SHOW_ATTENDING},{friendly: "Attending w/ Guests",action: SHOW_WITH_GUESTS}
      ];
    @Output() updateFilter : EventEmitter<string> = new EventEmitter<string>();
}

为view切分state
store可以想象成一个客户端数据库,因为在一个应用中,store是state状态的总和,我们需要能够对它进行查询,返回相关的状态切片和投影,这才是rxjs技术的store其精髓所在
要选择合适的状态片段进行处理,您可以通过使用经过自己的经典JavaScript集合操作的Rx实现来开始。 Store还提供了一个帮助函数select,它接受一个字符串或函数,在后台应用map和distinctUntilChanged返回一个Observable的相应状态。随着您的需求进步,RxJS提供了大量强大的操作符来满足任何用例。
没有合并操作的state

@Component({
    selector: 'app',template: `
      <h3>@ngrx/store Party Planner</h3>
      <party-stats
        [invited]="(people | async)?.length"
        [attending]="(attending | async)?.length"
        [guests]="(guests | async)"
      >
      </party-stats>
      <filter-select
        (updateFilter)="updateFilter($event)"
      >
      </filter-select>
      <person-input
        (addPerson)="addPerson($event)"
      >
      </person-input>
      <person-list
        [people]="people | async"
        [filter]="filter | async"
        (addGuest)="addGuest($event)"
        (removeGuest)="removeGuest($event)"
        (removePerson)="removePerson($event)"
        (toggleAttending)="toggleAttending($event)"
      >
      </person-list>
    `,PersonInput,FilterSelect,PartyStats]
})
export class App {
    public people;
    private subscription;

    constructor(
     private _store: Store
    ){
      this.people = _store.select('people');
      /* this is a naive way to handle projecting state,we will discover a better Rx based solution in next lesson */
      this.filter = _store.select('partyFilter');
      this.attending = this.people.map(p => p.filter(person => person.attending));
      this.guests = this.people
          .map(p => p.map(person => person.guests)
                     .reduce((acc,curr) => acc + curr,0));
    }
    //...rest of component

}

现在,我们拥有了所有需要的数据,我们能将其传递给本组件来呈现,我们的聪明组件将处理发射出来的任何action,在dispatching相应的event

可提交过滤器

@Component({
    selector: 'person-list',template: `
      <ul>
        <li 
          *ngFor="let person of people.filter(filter)"
          [class.attending]="person.attending"
        >
           {{person.name}} - Guests: {{person.guests}}
           <button (click)="addGuest.emit(person.id)">+</button>
           <button *ngIf="person.guests" (click)="removeGuest.emit(person.id)">-</button>
           Attending?
           <input type="checkBox" [checked]="person.attending" (change)="toggleAttending.emit(person.id)" />
           <button (click)="removePerson.emit(person.id)">Delete</button>
        </li>
      </ul>
    `,changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonList {
    @Input() people;
    //至此 我们下拉过滤器并提交
    @Input() filter;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}

运用 combineLatest 和 withLatestFrom

Observable.combineLastest()函数,总是合并序列中最新发射的值。宝珠图中的颜色球发射颜色,空白的图形发射待染色图形,处理函数对待染色对象进行染色:总是用户最新发射的颜色或者对最新发射的待染色对象。

和combineLatest()方法不同,withLatestFrom()方法仅在源序列输出元素时, 触发生成目标序列中的新元素

下面我们利用这两个方法来改写我们的代码

@Component({
    selector: 'app',template: `
      <h3>@ngrx/store Party Planner</h3>
      <party-stats
        [invited]="(model | async)?.total"
        [attending]="(model | async)?.attending"
        [guests]="(model | async)?.guests"
      >
      {{guests | async | json}}
      </party-stats>
      <filter-select
        (updateFilter)="updateFilter($event)"
      >
      </filter-select>
      <person-input
        (addPerson)="addPerson($event)"
      >
      </person-input>
      <person-list
        [people]="(model | async)?.people"
        (addGuest)="addGuest($event)"
        (removeGuest)="removeGuest($event)"
        (removePerson)="removePerson($event)"
        (toggleAttending)="toggleAttending($event)"
      >
      </person-list>
    `,PartyStats]
})
export class App {
    public model;

    constructor(
     private _store: Store
    ){
      /* Every time people or partyFilter emits,pass the latest value from each into supplied function. We can then calculate and output statistics. */
      this.model = Observable.combineLatest(
          _store.select('people')
          _store.select('partyFilter'),(people,filter) => {
          return {
            total: people.length,people: people.filter(filter),attending: people.filter(person => person.attending).length,guests: people.reduce((acc,curr) => acc + curr.guests,0)
          }
        });
    }
    //...rest of component
}

对选择器的重构
通过构建应用的课程,你将经常性的利用
1 类似的查询
2 在你的views中的状态投影( projections of state)
想消除这些重复逻辑,一个常见的方法是利用services,然后注入这些服务到其他的服务或组件。当然,这种方法有效,不过有一种更灵活的,可组合的方式来解决这个问题。
我们可以导出独立的小查询或选择器,不用放到Angular的service中,利用let操作符,无论是在组件 服务还是中间件中,我们都可以将这些选择器混合并匹配所需的结果。
这个高目的性的,可组合查询的工具箱称为选择器模式

我们建立一个新的文件来放我们的应用选择器。然后我们利用combineLatest 抽象正在被提供的投影函数,用它来过滤people和到过滤器中的产生的统计

Party模块选择器

export const partyModel = () => {
  return state => state
    .map(([people,filter]) => { return { total: people.length,0) } }); };

为了进一步示范,
让我们再创建两个选择器,一个返回一个可以参加的参与者,另一个是在前一个选择器的基础上,根据被邀请人计算参与人数的百分比。 这显示了将这些小型,集中选择器组合成用于视图和中间件的强大查询是多么容易。

出勤率选择器

export const attendees = () => {
  return state => state
    .map(s => s.people)
    .distinctUntilChanged();
};

export const percentAttending = () => {
  return state => state
    //build on prevIoUs selectors
    .let(attendees())
    .map(p => {
      const totalAttending = p.filter(person => person.attending).length;
      const total = p.length;
      return total > 0 ? (totalAttending / total) * 100 : 0;
    });
};

应用选择器很简单,只需将let操作符应用到相应的Observable,提供您所选择的选择器。

在容器组件中应用选择器

export class App {
    public model;

    constructor(
     private _store: Store
    ){
      /* Every time people or partyFilter emits,pass the latest value from each into supplied function. We can then calculate and output statistics. */
      this.model = Observable.combineLatest(
            _store.select('people'),_store.select('partyFilter')
          )
          //extracting party model to selector
          .let(partyModel());
      //for demonstration on combining selectors
      this.percentAttendance = _store.let(percentAttending());
    }
    //...rest of component
}

选择器接口

interface Selector<T,V> {
  (state: Observable<T>): Observable<V>
}

介绍store中间件

接口化元Reducer
一个单一的,不可变状态树的许多优点之一是易于实现的一般棘手的功能,如undo/redo。由于应用状态的进展通过商店的快照完全可视,通过这些快照回溯的能力变得微不足道。实现此功能的流行方法是通过 Meta-reducers。
尽管风评一般,元reducers在理论上和实现上其实相当简单。要创建一个Meta-reducer,将当前reducer放到父reducer中,通常通过父reducer委派大多数操作,只有在调度定义的元动作(如撤消/重做)时才dispatch。
这在实践中如何看待?我们来看看,为我们的派对规划应用程序创建一个重置功能,如果他们想要输入所有新的数据,允许用户从头开始。

要封装这个功能,我们创建一个工厂函数,接受任何reducer来包装,返回我们的reset reducer。当reset reducer初始化时,我们抓住父reducer的初始状态,保存以备以后使用。剩下的一切就是监听要发送的特定的重置动作。如果没有调度RESET_STATE,则动作将传递给包装的减速,并且状态返回正常。当RESET_STATE被触发时,返回存储的初始状态,而不是调用父reducer的结果。撤消/重做可以类似地处理,跟踪在当地状态的以前的动作。

重置元Reducer

export const RESET_STATE = 'RESET_STATE';

const INIT = '__NOT_A_REAL_ACTION__';

export const reset = reducer => {
    let initialState = reducer(undefined,{type: INIT})
    return function (state,action) {
      //if reset action is fired,return initial state
      if(action.type === RESET_STATE){
        return initialState;
      }
      //calculate next state based on action
      let nextState = reducer(state,action);
      //return nextState as normal when not reset action
      return nextState;
  }
}

在bootstrap中包裹Reducer

bootstrap(App,[ //wrap people in reset Meta-reducer provideStore({people: reset(people),partyFilter}) ]);

值得注意的是,store 的根reducer本身就是一个 Meta-reducer,当调用provideStore方法时,其实是调用combineReducers方法
对于每个dispatched的action,根reducer使用prevIoUs state和current action调用每个子reducer,返回一个[reducer]的object映射—- state[reducer]

combineReducers

export function combineReducers(reducers: any): Reducer<any> {
  const reducerKeys = Object.keys(reducers);
  const finalReducers = {};

  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key];
    }
  }

  const finalReducerKeys = Object.keys(finalReducers);

  return function combination(state = {},action) {
    let hasChanged = false;
    const nextState = {};
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i];
      const reducer = finalReducers[key];
      const prevIoUsStateForKey = state[key];
      const nextStateForKey = reducer(prevIoUsStateForKey,action);

      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== prevIoUsStateForKey;
    }
    return hasChanged ? nextState : state;
  };
}

猜你在找的Angularjs相关文章