ngrx实例
来源:互联网 发布:钱江晚报微信矩阵 编辑:程序博客网 时间:2024/06/06 00:04
前言
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.tsimport { 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({}, state, {guests: state.guests - 1}); } return state; case TOGGLE_ATTENDING: if(state.id === action.payload){ return Object.assign({}, state, {attending: !state.attending}); } return state; default: return state; }}//remember to avoid mutation within reducersexport 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> `, directives: [PersonList, 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}); } 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 to unsubscribe is no longer necessary}
利用ChangeDetection.OnPush
利用angular中的集中式状态树不仅可以带来可预测性和可维护性,还可以提高性能。为了实现此性能优势,我们可以使用OnPush的changeDetectionStrategy。
OnPush背后的概念很简单,当组件仅依赖输入时,这些输入引用不会改变,Angular可以跳过组件树的该部分的运行更改检测。如前所述,所有state的委托应在智能或顶级组件中处理。这使我们的应用程序中的大多数组件完全依赖于输入,安全地允许我们在组件定义中将ChangeDetectionStrategy设置为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';//根据所选过滤器返回适当的functionexport 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 Constantsexport 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_ALL, 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> `, directives: [PersonList, 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> `, directives: [PersonList, PersonInput, FilterSelect, 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, people: people.filter(filter), attending: people.filter(person => person.attending).length, guests: people.reduce((acc, curr) => acc + curr.guests, 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; };}
- ngrx实例
- ngrx/store
- ngrx api
- ngrx 初探
- Angular2 之 @ngrx/store
- angular4实战(4)ngrx
- Ngrx、RxJs、Redux的初步探究
- 使用Redux和ngrx构建更好的Angular2应用(一)
- 使用Redux和ngrx构建更好的Angular2应用(二)
- 使用Redux和ngrx构建更好的Angular2应用(三)
- 使用Redux和ngrx构建更好的Angular2应用(四)
- 【译】手把手教你用ngrx管理Angular状态
- 翻译:使用 Redux 和 ngrx 创建更佳的 Angular 2
- 翻译:使用 Redux 和 ngrx 创建更佳的 Angular 2
- 实例
- 实例
- 实例
- 实例
- 单源最短路问题 Codevs 1557 热浪(含讲解)
- html1、2两天补充
- mysql索引
- Android View事件传播机制
- gcc 编译过程和编译优化
- ngrx实例
- 电路第一章知识点总结(上)
- 协方差和想关系数理解
- txt导入mysql
- linux命令--查找与统计(grep、awk、sort、uniq、wc)
- __builtin_expect 解惑
- 学生中遇到的问题(一)
- 微信公众号开发——1、搭建服务器+与用户交流
- yolov1