日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

ngrx/store綜合介紹,讀完馬上精通angular中的ngrx/store

 loudf 2019-02-24

像傳統(tǒng)數(shù)據(jù)一樣,代表著應(yīng)用的記錄點(diǎn),你的Store可以被認(rèn)為是客戶端"真實(shí)的數(shù)據(jù)來源" 或 數(shù)據(jù)庫。在設(shè)計(jì)應(yīng)用時(shí)都遵守一個(gè)Store的約定,Store任何時(shí)刻的存儲快照都將只是呈現(xiàn)應(yīng)用程序的完整狀態(tài)。

一個(gè)單一的、不可變的狀樹,只有通過顯式定義和調(diào)度才能更新。

中心化,不可變狀態(tài)

Reducers

Store應(yīng)用程序的第二個(gè)組成部分是reducers。A2 reducer 是一個(gè) a3純函數(shù),前一個(gè)狀態(tài)和一個(gè)與事件相關(guān)聯(lián)的類型和可選數(shù)據(jù)(playload)的Action。使用以前的說法是,如果Store被認(rèn)為是客戶端的數(shù)據(jù)庫, 則reducers可以被認(rèn)為是數(shù)據(jù)庫中數(shù)據(jù)表。Reducers代表著應(yīng)用程序的部分或狀態(tài)片段,應(yīng)相應(yīng)的進(jìn)行結(jié)構(gòu)化和組合。

Reducer 接口2
export interface Reducer<T> {
  (state: T, action: Action): T;
}

A3函數(shù)的返回值類型由其輸入值的類型決定的。

一個(gè)簡單的Reducer
export const counter: Reducer<number> = (state: number = 0, action: Action) => {
  switch(action.type) {
    case 'INCREMENT':
        return state + 1;
    case 'DECREMENT':
        return state -1;
    default:
        return state;
  }
}
Actions

Store包含了我們應(yīng)用程序的state和Reducers的輸出部分,但是當(dāng)狀態(tài)需要更新時(shí),我們?nèi)绾闻creducers通信呢?這是actions4,在Store應(yīng)用程序中,所有導(dǎo)致狀態(tài)更新的用戶交互都必須以actions的形式表示。所有與用戶相關(guān)的事件都被分派為action,經(jīng)過Store的4個(gè)action通道,然后輸出一個(gè)新的狀態(tài)表示。每次調(diào)度一個(gè)action時(shí)都會發(fā)生這個(gè)過程,留下應(yīng)用程序狀態(tài)隨時(shí)間變化的完整的,可序列話的表示形式。

Action接口4
export interface Action {
    type: string;
    payload?: any;
}
派發(fā)Action的流水線5

Actions簡單示例
//沒帶數(shù)據(jù)的action
dispatch({type: 'DECREMENT'});

//帶數(shù)據(jù)的action
dispatch({type:ADD_TODO, payload: {id: 1, message: 'Learn ngrx/store', completed: true}});
數(shù)據(jù)投影

最后,我們需要從Store中提取、組合和投影數(shù)據(jù)以顯示在我們的視圖中。因?yàn)镾tore本身是可觀察的,所以我們可以訪問你習(xí)慣的典型JS集合操作(map, filter, reduce等)以及強(qiáng)大的基于RxJS的可觀察操作符。這將使得將Store數(shù)據(jù)分割成你希望很容易的投影。

狀態(tài)投影
//最簡單的示例,從state獲取people
store.select('people');

//合并多個(gè)state
Observable.combineLatest(
    store.select('people'),
      store.select('events'),
      (people, events) => {
        // 在此投影
    }
)

Not Your Classic Angular

在上一節(jié)中,我提到了在開發(fā)應(yīng)用程序時(shí)遵守的約定。在傳統(tǒng)的設(shè)置和工作流程,你已經(jīng)習(xí)慣了嗎,這是什么意思?讓我們一起來看看。

如果你是從Angular1轉(zhuǎn)過來的,你會很熟悉數(shù)據(jù)的雙向綁定6??刂破靼裮odel綁定到視圖,反之亦然。這種方法的問題出現(xiàn)在你的視圖變得更新復(fù)雜時(shí),需要控制器和指令來管理并表示重要的狀態(tài)隨時(shí)間的變化。這很快變成一個(gè)噩夢,無論是推理還是調(diào)度,因?yàn)橐粋€(gè)變化會影響另一個(gè)變化,另一個(gè)又影響另一個(gè)......等等。

Store提升了單向數(shù)據(jù)流7和顯式調(diào)度操作的概念。的感受狀態(tài)更新都緩存在組件中,委托給reducer。在應(yīng)用程序中啟動狀態(tài)更新的唯一辦法是通過調(diào)度操作,對應(yīng)于特定的reducer案例。這不僅使你應(yīng)用程序的狀態(tài)改變變得簡單,因?yàn)楦率羌显谝黄鸬?,它會在出現(xiàn)錯(cuò)誤時(shí)留下清晰的線索。

雙向數(shù)據(jù)綁定6
6 Two-Way Data Binding

單向數(shù)據(jù)綁定

不使用Store的Counter示例

(在線演示)

@Component({
    selector:'counter',
    template: `
        <div class='counter'>
            <button (click)='increment()'>+</button>
            <button (click)='decrement()'>-</button>
            <h3>{{counter}}</h3>
        </div>
    `
})
export class Counter {
    counter = 0;
      
      increment() {
        this.counter += 1;
    }
  
      decrement() {
        this.counter -= 1;
    }
}
使用Store的Counter示例

(演示)

@Component({
    selector: 'counter',
      template: `
        <div class='content'>
            <button (click)="increment()">+</button>
            <button (click)="decrement()">-</button>
        </div>
    `,
      changeDetection: ChangeDetectionStrategy.OnPush
})
export class Counter {
    counter$: Observable<number>;
  
      constructor(
          private store: Store<number>
      ){
         this.counter$ = this.store.select('counter');       
     }
  
      increment(){
        this.store.dispatch({type:'INCREMENT'});
    }
  
      decrement(){
        this.store.dispatch({type:'DECREMENT'});
    }
}
Store的優(yōu)勢

在整個(gè)概述中,我們簡要介紹了利用Store在一種典型的Angular 1風(fēng)格方法的優(yōu)勢,現(xiàn)在讓我們發(fā)一點(diǎn)時(shí)間來回顧一下。為什么要花時(shí)間在這個(gè)特定的庫,構(gòu)建和曲線上投資呢?Store的優(yōu)勢是狀態(tài)中心化,性能,測試。

中心化,狀態(tài)不可變

所有相關(guān)應(yīng)用程序的狀態(tài)都緩存在一個(gè)位置。這樣可以很容易地跟蹤問題,因?yàn)殄e(cuò)誤時(shí)的狀態(tài)快照可以提供重要的見解,并且可以輕松的重新重現(xiàn)這個(gè)問題。這也使得眾多困難問題,例如在Store應(yīng)用程序的上下文中撤消/重做某一步驟,并且實(shí)現(xiàn)了更強(qiáng)大的功能的工具。

性能

由于狀態(tài)集合中應(yīng)用程序的頂層,因?yàn)閿?shù)據(jù)更新可以通過組件依賴于Store。Angular構(gòu)建如這樣的數(shù)據(jù)流布置進(jìn)行優(yōu)化,并且可以在組件依賴于沒有發(fā)布新值的Observables的情況下禁用變化檢測。在最佳的緩存解決方案中,這將是絕大多數(shù)組件。

測試

所有狀態(tài)更新都是在recudes中處理的,它們是純函數(shù)。純函數(shù)測試非常簡單,因?yàn)樗皇禽斎?,反對輸出。這樣可以測試應(yīng)用程序中最關(guān)鍵的方面,而無需使用mock,或其他的測試技巧,可以使測試復(fù)雜且容易出錯(cuò)。

工具與生態(tài)系統(tǒng)

中心化的,不可變的狀態(tài)還可以實(shí)現(xiàn)更強(qiáng)大的工具。一個(gè)這樣的盒子是ngrx開發(fā)工具,它提供了action和狀態(tài)變化的歷史,允許在開發(fā)過程中進(jìn)行8次遍歷。Store提供的模式還允許一個(gè)易于實(shí)現(xiàn)中間件的豐富的生態(tài)系統(tǒng)。因?yàn)镾tore在分派action之前和之后都提供一個(gè)入口點(diǎn),所以應(yīng)用程序減少,如同步片狀態(tài)到本地Store,高級日志記錄和實(shí)現(xiàn)sagas這樣的問題可以通過快速包和幾行代理來解決。這個(gè)生態(tài)系統(tǒng)只會在未來幾個(gè)月內(nèi)增長。

操作調(diào)度action和狀態(tài)更改的歷史 ,以模擬應(yīng)用程序交互的時(shí)間點(diǎn)。

@ngrx/store的構(gòu)建模塊

在構(gòu)建Store應(yīng)用程序之前,首先來看看構(gòu)建@ngrx/store的RxJS概念。首先理解這些概念,我們將來可以更有效地利用這個(gè)庫。要詳細(xì)說明下面的每個(gè)主題,請查看這些額外的資源。

聲明:Mike Ryan和Rob Wormald的實(shí)際@ngrx/store代碼顯著更強(qiáng)大。這些示例旨在演示涉及的RxJS概念,并從庫中移出"magic"

Subject/Dispatch的探索

Rx的信使們,你告訴我,我會告訴他們的.....

(演示)

@ngrx/store的兩個(gè)支柱,Store和Dispatcher都擴(kuò)展了RxJS主題。主題即是觀察者(Observables)和觀察者(Observers),這意味著你可以訂閱Subject,但也可以將主題訂閱源。在高級別科目可以被認(rèn)為是信使或代理人。

因?yàn)镾ubject是Observables,你可以 "next" 或直接將值傳遞到流中。然后,該Subject的訂閱將被通知發(fā)出值。在Store的上下文中,這些用戶可能是一個(gè)Angular 服務(wù), 組件或需要訪問應(yīng)用程序狀態(tài)的任何內(nèi)容。

訂閱主題
//創(chuàng)建一個(gè)主題
const mySubject = new Rx.Subject();

//添加訂閱者
const subscriberOne = mySubject.subscribe(val => {
    console.log('***SUBSCRIBER ONE***',val);
});

const subscriberTwo = mySUbject.subscribe(val => {
    console.log('***SUBSCRIBER TWO***',val);
});

//發(fā)射subject的值到observers
mySubject.next('FIRST VALUE!');// ***SUBSCRIBER ONE*** FIRST VALUE! ** SUBSCRIBER TWO*** FIRST VALUE!
mySubject.next('SECOND VALUE!');//***SUBSCRIBER ONE*** SECOND VALUE! ***SUBSCRIBER TWO*** SECOND VALUE

在Store或Redux中,將action發(fā)送到應(yīng)用程序中的Store是一種慣例。為了維護(hù)此API,Dispatcher擴(kuò)展至Subject,將派生方法作為傳遞添加到傳統(tǒng)的下一個(gè)方法。這被用于將值發(fā)送到Subject中,然后將這些值發(fā)送給子對象。

將Dispatcher繼承自Subject
/*
redux/ngrx-store 有一個(gè)dispatcher的概念,或者是面向應(yīng)用程序Store發(fā)送操作的方法允許擴(kuò)展Rx.Subject與我們的Dispatcher類來維護(hù)熟悉的術(shù)語。
*/

//從Subject中繼承
class Dispatcher extends Rx.Subject {
    dispatcher(value: any): void{
        this.next(value);
    }
}

//創(chuàng)建一個(gè)dispatcher(只是一個(gè)包含next的SUbject方法)
const dispatcher = new Dispatcher();

//添加訂閱
const subscribeOne = dispatcher.subscribe(val => {
    console.log('***SUBSCRIBER ONE***', val);
});

const subscribeTwo = dispatcher.subscribe(val => {
    console.log('***SUBSCRIBER TWO***', val);
});

//將值發(fā)射到observers
dispatcher.dispatch('FIRST DISPATCHED VALUE!');
dispatcher.dispatch('SECOND DISPATCHED VALUE!');
BehaviorSubject/Store探索

與Subject類似,但你說的最后一件事是什么?...

(演示)

雖然Subject作為dispatcher完美地工作,但它們有一個(gè)問題可以防止他們適合Store。訂閱Subject時(shí),只接收訂閱后發(fā)出的值。在不斷添加和刪除組件的環(huán)境中,這是不可接受的,在訂閱時(shí)需要應(yīng)用程序Store的最新的按需狀態(tài)部分。

Subjects只接受訂閱后發(fā)出的值

/*
現(xiàn)在我們有一個(gè)dispatcher, 讓我們創(chuàng)建我們的Store來接收已經(jīng)發(fā)送的action。
*/

class FirstStore extends Rx.Subject{}

const myFirstStore = new FirstStore();

//添加訂閱者
const subscriberOne = myFirstStore.subscribe(val => {
    console.log('***SUBSCRIBER ONE***', val);
});
const subscriberTwo = myFirstStore.subscribe(val => {
    console.log('***SUBSCRIBER TWO***', val);
});

//現(xiàn)在,讓超級dispatcher發(fā)布值到store
myFirstStore.next('FIRST VALUE!');

/*
我們在添加一個(gè)訂閱者。
由于我們第一次實(shí)施Store是一個(gè)subject,訂閱者只能看到發(fā)布的價(jià)值*AFTER*他們訂閱之后。在這種情況下,訂閱者3將不了解'FIRST VALUE!'
*/
const subscriberThree = myFirstStore.suscribe(val => {
    console.log('***SUBSCRIBER THREE***', val);
});

幸運(yùn)的是,RxJS為Subject處理這個(gè)問題提供了BehaviorSubject。 即BehviorSubject 封裝了Subject的所有功能,但也可以在訂閱后將改后發(fā)布的值返回給訂閱都。這意味著組件和服務(wù)將始終可以訪問最新(或初始值)應(yīng)用程序狀態(tài)和所有將來的更新。

BehaviorSubject訂閱接收上一次發(fā)布的值
/*
因?yàn)槲覀兊慕M件需要查詢當(dāng)前狀態(tài),所以BehaviorSubject更適合Store。BehaviorSubjects具有Subject的所有功能,還允許設(shè)置初始值,以及在訂閱時(shí)將所接收的最后一個(gè)值輸出給所有觀察者。
*/
class Store extends Rx.BehaviorSubject {
    constructor(initialState: any){
        super(initialState);
    }
}

const store = new Store('INITIAL VALUE');

//添加一些訂閱者
const storeSubscriberOne = store.subscribe(val => {
    console.log('***STORE SUBSCRIBER ONE***', val);
});

//為了演示,手動發(fā)布值到store
const storeSubscriberTwo = store.subscribe(val => {
    console.log('***STORE SUBSCRIBER TWO***', val);
});

//在'FIRST VALUE!' 發(fā)布之后添加另一個(gè)訂閱者
//輸出:***STORE SUBSCRIBER THREE*** FIRST STORE VALUE!
const subscriberThree = store.subscribe(val => {
    console.log('***STORE SUBSCRIBER THREE***', val);
});
Store + Dispatcher數(shù)據(jù)流

單狀態(tài)樹和單向數(shù)據(jù)流在Angular ...

(演示)

為了store的上下文正常運(yùn)行,dispatcher仍然需要一些工作。在Store應(yīng)用程序中,所有dispatch的action必須通過特定的管道傳遞,才能將新的狀態(tài)表示傳遞到store中,并發(fā)送給所有觀察者。你可以將此視為工廠裝配線,在這種情況下,線上的站是pre-middleare->reducers->post->middleware->store。

這個(gè)流水線的創(chuàng)建是在創(chuàng)建時(shí)dispatch傳遞給store處理的。然后,store下一個(gè)方法被覆蓋,以便將新的狀態(tài)表示傳遞到store之前,首先將所有的action都dispatch管道。這也允許通過dispatch匯集接收到的action。

現(xiàn)在,中間件和reducers的實(shí)現(xiàn)將被刪除。

將Dispatcher與Store關(guān)聯(lián)一起
/*
所有action都應(yīng)通過管道,然后新計(jì)算的狀態(tài)通過store。
1.) Dispatched Action
2.) Pre-Middleware
3.) Reducers (return new state)
4.) Post-Middleware
5.) store.next(newState)
*/
class Dispatcher extends Rx.Subject{
    dispatcher(value: any): void{
        this.next(value);
    }
}

class Store extends Rx.BehaviorSubject{
    constructor(
          private dispatcher,
         initialState
      ){
            super(initialState);
          /*
          所有dispatch的action在通過新狀態(tài)之前 通過action管道傳遞到store
          */
          this.dispatcher
              //pre-middleware
              //reducers
              //post-middleware
              .subscribe(state => super.next(state));
        }
    
      //首先通過分派action到管道并委托給store.dispatch
      dispatch(value){
        this.dispatcher.dispatch(value);
    }
    
      //覆蓋store允許直接訂閱action注通過store
      next(value){
        this.dispatcher.dispatch(value);
    }
}

const dispatcher = new Dispatcher();
const store = new Store(dispatcher,'INITIAL STATE');

const subscriber = store.subscribe(val => console.log('VALUE FROM STORE: ${val}'));

/*
所有分派action首先流經(jīng)管道,計(jì)算新狀態(tài)然后傳遞到store??偨Y(jié)一下,我們的理想行為分派action->premiddleware->reducers->post-middleware->store.next(newState)
*/
//兩種方法在幕后都是相同的
dispatcher.dispatch('DISPATCHED VALUE!');
store.dispatch('ANOTHER DISPATCHED VALUE!');

const actionStream$ = new Rx.Subject();

/*
覆蓋store下一個(gè)方法允許我們將store直接訂閱到action流,提供與手動調(diào)用store.dispatch或dispatcher.dispatch相同的行為
*/
actionStream$.subscribe(store);
actionStream$.next('NEW ACTION!');
什么是Reducer?

像雪球一樣下滑,reducer通過迭代累加...

(演示)

Reducers是基于任何store或Redux的應(yīng)用基礎(chǔ),描述基于分派action類型的狀態(tài)部分及其潛在轉(zhuǎn)換。你的reducer的組合是在任何給定時(shí)間組成應(yīng)用程序狀態(tài)的表示。

在討論如何創(chuàng)建和實(shí)現(xiàn)reducers之前 , 我們先來看看reduce函數(shù)。reduce需要一個(gè)數(shù)組,根據(jù)累加值和當(dāng)前值運(yùn)行一個(gè)函數(shù),在完成后將數(shù)組遞減一個(gè)值。你可以把reducers看成一個(gè)滾雪而下的雪橇,每一次變革都會變得很大。以相同的方式,減少reduce是通過迭代定義的函數(shù)應(yīng)用于當(dāng)前值的結(jié)果。

標(biāo)準(zhǔn)的Reduce
/*
你可以想一下滾雪球的場景。每一次翻滾都會累加質(zhì)量和體積直到到達(dá)底部。reduce也類似,返回的值傳遞給所有提供函數(shù)的下一個(gè)調(diào)用,直到源數(shù)組中的所有值都耗盡為止。讓我們看看一些鞏固概念的盒子。
*/
const numberArray = [1,2,3];

/*
1.) accumulator:1, current:2
2.) accumulator:3, current:3
Final: 6
*/
const total = numberArray.reduce((accumulator, current) => accumulator + current);
console.log('***TOTAL***:',${total});

//reduce操作的對象
const personInfo = [{name:'Joe'},{age:31},{birthday:'1/1/1985'}];

/*
1.) accumulator: {name: 'Joe'}, current: {age: 31}
2.) accumulator: {name: 'Joe', age:31}, current: {birthday: '1/1/1985'}
Final: {name: 'Joe', age:31, birthday: '1/1/1985'}
*/
const fullPerson = personInfo.reduce(accumulator, current) => {
    return Object.assign({}, accumulator, current);
}
console.log('*** FULL PERSON***:',fullPerson);

const personInfoStart = [{name:'Joe'},{age: 31},{birthday:'1/1/1985'}];

/*
1.) accumulator: {favoriteLangue: 'JavaScript'}, current: {name: 'Joe'}
2.) accumulator: {favoriteLangue: 'JavaScript', name: 'Joe'}, current: {age: 31}
3.) accumulator: {favoriteLange: 'JavaScript', name: 'Joe', age: 31}, current: {birthday: '1/1/1985'}
Final: {favoriteLangue: 'JavaScript', name: 'Joe', age: 31, birthday: '1/1/1985'}
*/
const fullPersonStart = personInfo.reduce((accumulator, current) => {
    return Object.assign({}, accumulator, current);
},{favoriteLangue:'JavaScript'});
console.log('***FULL PERSON START:', fullPersonStart);

受Redux的啟發(fā),@ngrx/store具有操縱特定狀態(tài)的Reducer功能的概念。Reducer接受一個(gè)state和action作為參數(shù),暴露一個(gè)switch語句(一般來說,盡管 這可以通過多種方式處理)定義reducer所涉及的action類型。每次分派一個(gè)action時(shí),將調(diào)用注冊到store的每個(gè)reducer(通過根reducer, 在應(yīng)用程序引導(dǎo)時(shí)在provideStore中創(chuàng)建),傳遞該狀態(tài)片段(累加器)的當(dāng)前狀態(tài)和已分派的action。如果reducer沒有被注冊來處理該action類型,則將執(zhí)行適當(dāng)?shù)臓顟B(tài)計(jì)算和狀態(tài)輸出的表示。如果 沒有那么該部分的當(dāng)前 狀態(tài)將被返回。這是Store和Redux的狀態(tài)管理核心。

Store / Redux 風(fēng)格的Reducer
// Redux風(fēng)格的Reducer
const person = (state = {}, action ) => {
    switch(action.type){
      case 'ADD_INFO':
            return Object.assign({}, state, action.payload);
      default:
            return state;
    }
}

const infoAction = {type: 'ADD_INFO', payload: {name:'Brian', framework:'Angular'}};
const anotherPersonInfo = person(undefined, infoAction);
console.log('***REDUX STYLE PERSON***:', anotherPersonInfo);

//添加其他reducer
const hoursWorked = (state = 0, action) => {
    switch(action.type) {
      case 'ADD_HOUR':
            return state + 1;
      case 'SUBTRACT_HOUR':
            return state -1;
      default:
            return state;
    }
}

//組合Reducers更新數(shù)據(jù) 
const myReducers = { person, hoursWorked};
const combineReducers = reducers => (state = {}, action) => {
    return Object.keys(reducers).reduce((nextState, key) => {
        nextState[key] = reducers[key](state[key],action);
      return nextState;
    }, {});
};

/*
這讓我們大部的方式在那里,但真正希望我們想要的是第一個(gè)和第二個(gè)的值累加隨著action隨著時(shí)間推移。幸運(yùn)的是,RxJS為這處情況提供了完美的操作符,將在下一課中討論。
*/
const rootReducer = combineReducers(myReducers);
const firstState = rootReducer(undefined, {type:'ADD_INFO', payload:{name: 'Brian'}});
const secondState = rootReducer({hoursWorked: 10, person: {name: 'Joe'}},{type:'ADD_HOUR'});
console.log('***FIRST STATE***:',firstState);
console.log('***SECOND STATE***:',secondState);
通過根Reducer分派action9

使用scan操作符聚合狀態(tài)

類似于reduce,但值隨著時(shí)間的推移累加。。。

(scan demo | demo)

scan操作符以類似的方式扮作reduce,除了累加器隨時(shí)間保持,或直接scan應(yīng)用的可觀察完成。例如,當(dāng)分派action和新的狀態(tài)輸出時(shí),scan函數(shù) 中的累加器將始終是狀態(tài)的最后一個(gè)輸出表示形式。這減輕了需要維護(hù)store中的狀態(tài)副本以傳遞給我們的reducer。

scan操作符的基本示例
const testSubject = new Rx.Subject();
//scan示例,從0開始每次累加
const basicScan = testSubject.scan((acc, curr) => acc+ curr, 0);
// 記錄累加值
const subscribe = basicScan.subscribe(val => console.log('Accumulated total:', val));
//傳遞值到我們的testSubject,并累加當(dāng)前值
testSubject.next(1);//1
testSubject.next(2);//2
testSubject.next(3);//3

const testSubjectTwo = new Rx.Subject();
// scan示例隨著時(shí)間的推移建立對象
const objectScan = testSubjectTwo.scan((acc, curr) => Object.assign({}, acc,curr), {});
// 記錄累加值
const subscribe = objectScan.subscribe(val => console.log('Accumulated object:', val));
//傳遞值到testSubject,添加屬性到一個(gè)新對象
testSubjectTwo.next({name: 'Joe'});
testSubjectTwo.next({age: 30});
testSubjectTwo.next({favoriteFramework: 'Angular 2'});// {name: 'Joe', age: 30, favoriteFramework: 'Angular 2'}

為了在應(yīng)用程序store中使用scan,它只需要操作符應(yīng)用于dispatcher程序。所有分派的action都將通過scan,調(diào)用具有當(dāng)前state和action組合的reducer,輸出新的狀態(tài)表示。然后,就的應(yīng)用程序狀態(tài)被關(guān)閉,或被推送到store,并發(fā)送給所有訂閱者。

使用scan做store存儲
class Store extends Rx.BehaviorSubject{
    constructor(
          private dispatcher,
         private reducer,
         initialState = {}
      ){
            super(initialState);
          this.dispatcher
                // pre-middleware?
              /*
              前面我們把reduce比喻成一個(gè)雪球,越滾越大(或在原來的基礎(chǔ)上累加)。scan也是類似行為。累加器(在這個(gè)示例中是,state)它將會繼續(xù)累加前面的數(shù)據(jù)直到終止。這使得他它成為管理應(yīng)用程序狀態(tài)的理想操作符。
              */
          .scan((state, action) => this.reducer(state, action), initialState)
          //post-middleware?
          .subscribe(state => super.next(state));
        }
          // ... store implementation
}
使用let管理中間件

讓我擁有整個(gè)可觀察的。。。

(let demo | store demo)

中間件已經(jīng)在ngrx/store v2中移出了。通這個(gè)部分來閱讀本書,以了解let操作符,因?yàn)樗梢耘c選擇器一起使用。

雖然大多數(shù)運(yùn)算符都是從可觀察的值傳遞出來的,但是我們可以把整個(gè)可觀察的數(shù)據(jù)傳遞出去在返回源可觀察數(shù)據(jù)之前,這允許有機(jī)會處理額外的操作符和功能。雖然這可能看起來像一個(gè)小小的細(xì)微差別,但它完全適合于中間件或選擇器(稍后討論)的情況,消費(fèi)者想要定義一個(gè)可利用的,可重復(fù)使用的代碼塊,以插入到可觀察鏈中的特定時(shí)隙。

let 的基本功能
const myArray = [1,2,3,4,5];
const myObservableArray = Rx.Observable.fromArray(myArray);

const test = myObservableArray
    .map(val => val +1)
    //這里會失敗,let 與多數(shù)的操作符不一樣
    //.let(val => val + 2)
    .subscribe(val => console.log('VALUE FROM ARRAY:', val));

const letTest = myObservableArray
    .map(val => val +1)
    //let 操作符擁有整個(gè)observable
    .let(obs => obs.map(val => val +2))
    .subscribe(val => console.log('VALUE FROM ARRAY WITH let :', val));

//let 操作符提供靈活度,添加多個(gè)操作符到源observable然后返回
const letTestThree = myObservableArray
    .map(val => val +1)
    //let 操作符擁有整個(gè)observable
    .let(obs => obs
            .map(val => val +2)
            .filter(val => val % 2 === 0)
     )
    .subscribe(val => consle.log('let WITH MULTIPLE OPERATORS:', val));

//傳遞你的函數(shù)來添加操作符到observable
const obsArrayPlusYourOperators = (yourAppliedOperators) => {
    return myObservableArray
          .map(val => val +1 )
          .let(yourAppliedOperators)
};

const addTenThenTwenty = obs => obs.map(val => val + 10).map(val => val + 20);
const letTestFour = obsArrayPlusYourOperators(addTenThenTwenty)
    .subscribe(val => console.log('let FROM FUNCTION:', val));

let 操作符非常適合@ngrx/store中間件,因?yàn)橛脩粼趓educer輸出狀態(tài)之前 或之后添加自定義功能需要一個(gè)入口點(diǎn),這是在@ngrx/store中如何應(yīng)用前后中間件的基礎(chǔ)。

添加let 操作符到中間件的入口
class Store extends Rx.BehaviorSubject{
    constructor(
      private dispatcher,
     private reducer,
     preMiddleware,
     postMiddleware,
     initialState = {}
  ){
        super(initialState);
      this.dispatcher
          //let 操作符接受整個(gè)源observable,返回一個(gè)新的observable
          //@ngrx/store 組成中間件,所以你可以提供多個(gè)功能
          //在我們下面的示例中,會接受一個(gè)前件和一個(gè)后件
          //中間件標(biāo)識:(obs) => obs
      .let(preMiddleware)
      .scan((state, action) => this.reducer(state,action),initialState)
      .let(postMiddleware)
      .subscribe(state => super.next(state));
    }
  // ... store implementation
}

const preMiddleware = obs => { return obs.do(val => console.log('ACTION:', val))};
const postMiddleware = obs => {return obs.do(val => console.log('STATE:', val))};

... create store supplying middleware
我們來回顧一下之前的內(nèi)容:
  • 分派的action進(jìn)入dispatch(Subject)
  • Dispatcher有3個(gè)操作符:

    • let - 傳遞一個(gè)action的observable
    • scan - 調(diào)用每個(gè)reducer的當(dāng)前state和action,返回新的state
    • let - 傳遞一個(gè)state 的observable
  • 新返回的state會通過next方法把值傳遞到store(HehaviorSubject),被發(fā)射到訂閱者

這是store內(nèi)部工作的要點(diǎn)。

使用map 操作符修改state

我會把復(fù)雜的片段...

(demo)

從集合中投影數(shù)據(jù)的基石功能是map。map將指定的函數(shù) 應(yīng)用于一項(xiàng),返回該薦的新結(jié)果。因?yàn)閼?yīng)用程序狀態(tài)的鍵/值對象映射,所以提供一個(gè)輔助函數(shù) 來簡單的返回基于字符 串或任何其他相關(guān)選擇器的請求的狀態(tài)片段。

通過map操作符轉(zhuǎn)換state
class Dispatcher extends Rx.Subject{
    dispatch(value: any) : void{
        this.next(value);
    }
}

class Store extends Rx.BehaviorSubject{
    constructor(
          private dispatcher,
         private reducer,
         preMiddleware,
         postMiddleware,
         initialState = {}
      ){
            super(initialState);
          this.dispatcher
              .let(preMiddleware)
              .scan((state,action) => this.reducer(state, action), initialState)
              .let(postMiddleware)
              .subscribe(state => super.next(state));
        }
  
  
        //map 可以很容易的選擇組件所需的狀態(tài)片段
        //這個(gè)一種簡單的輔助功能,使得state的抓取部分更加簡潔
        select(key:string){
            return this.map(state => state[key]);
        }

        // ... store implemenetation
}
//... create store

//使用store的select輔助
const subscriber = store
    .select('person')
    .subscribe(val => console.log('VALUE OF PERSON:', val));
使用distinctUntilChanged管理狀態(tài)更新

改變之前不要調(diào)用我

(distinctUntilChanged demo | store demo)

我們應(yīng)用程序中的每個(gè)視圖都只關(guān)心自己的狀態(tài)片段。由于性能原因,我們不希望從所選狀片段中發(fā)出新值,除非已進(jìn)行更新。幸運(yùn)的是,對于我們來說,RxJS有一個(gè)很好用的操作符做這事(注意趨勢)。distinctUntilChanged操作符將僅在下一個(gè)值為唯一時(shí)基于先前發(fā)出的值發(fā)出。在數(shù)字和字符串的情況下,這意味著相等的數(shù)字和字符串,在對象的情況下,如果 對象引用是相同的新對象將不會被發(fā)出。

在基本類型與引用類型上使用distinctUntilChanged
//只會輸出唯一的值,同時(shí)是基于最新發(fā)出的值
const myArrayWithDuplicateInARow = new Rx.Observable    
    .fromArray([1,1,2,2,3,1,2,3]);

const distinctSub = myArrayWithDuplicatesInARow
    .distinctUntilChanged()
    //output: 1,2,3,1,2,3
    .subscribe(val => console.log('DISTINCT SUB:',val));

const nonDistinctSub = myArrayWithDuplicatesInARow
    //output: 1,1,2,2,3,1,2,3
    .subscribe(val => console.log('MON DISTINCT SUB:', val));

const sampleObject = {name: 'Test'};

const myArrayWithDuplicateObjects = new Rx.Observable.fromArray([sampleObject,sampleObject,sampleObject]);

//只輸出唯一的對象,同時(shí)是基于最新發(fā)出的值
const nonDistinctObjects = myArrayWithDuplicateObjects
    .distinctUntilChanged()
    //output: 'DISTINCT OBJECTS: {name: 'Test'}
    .subscribe(val => console.log('DISTINCT OBJECTS:',val));

回想一下,store的reducers始終具有默認(rèn)的情況,如果與分派的action不相關(guān),則返回上一個(gè)狀態(tài)。這意味著,在應(yīng)用程序中選擇狀態(tài)片段時(shí),除非更新了特定的片段,否則不會接收更新。這有助于使你的Store應(yīng)用程序更有效率。

在Store中使用distinctUntilChanged
class Dispatcher extends Rx.Subject {
    dispatch(value:any) : void{
        this.next(value);
    }
}

class Store extends Rx.BehaviorSubject{
    constructor(
          private dispatcher,
         private reducer,
         preMiddleware,
         postMiddleware,
         initialState = {}
      ){
            super(initialState);
          this.dispatcher
              .let(preMiddleware)
              .scan((state, action) => this.reducer(state,action), initialState),
            .let(postMiddleware)
              .subscribe(state => super.next(state));
        }
  
  /*
  distinctUntilChanged僅在輸出不同時(shí)才發(fā)出新值,最后發(fā)出的值。在下面的示例中,distinctUntilChanged操作符的可觀察值將發(fā)送一個(gè)較小的值,而另一個(gè)僅使用map操作符。
  */
  select(key:string){
      return this.map(state => state[key])
                .distinctUntilChanged();
  }
}

// add reducers
  // configure store
  
  const subscriber = store
    //使用distinctUntilChanged
    .select('person')
    .subscribe(val => console.log('PERSON WITH DISTINCTUNTILCHANGED:', val));

const subscriberTwo = store
    //沒有使用distinctUntilChanged, 將會打印數(shù)據(jù) 
    .map(state => state.person)
    .subscribe(val => console.log('PERSON WITHOUT DISTINCTUNTILCHANGED:', val));

    // dispatch a few actions
    dispatcher.dispatch({
        type:'ADD_INFO',
      payload:{
          name:'Brian',
        message:'Exporing Reduce!'
      }
    });

// person不會被改變
dispatcher.dispatch({
    type:'ADD_HOUR'
});

演練

一個(gè)簡單的應(yīng)用程序

我們將要?jiǎng)?chuàng)建的示例程序10是一個(gè)簡單入門的應(yīng)用。用戶應(yīng)該能夠輸入?yún)⒓诱呒捌淇腿说牧斜?,跟蹤認(rèn)證確認(rèn)出席者,通過特定標(biāo)準(zhǔn)過濾與會者,并快速查看有關(guān)活動的重要統(tǒng)計(jì)信息。在整個(gè)應(yīng)用程序的創(chuàng)建過程中,我們將探討@ngrx/stre的核心概念,并討論流行的模式和最佳做法。

在每個(gè)部分上方提供了兩個(gè)鏈接 ,工作和完成課程。如果 你希望按照概念進(jìn)行編碼,則每個(gè)課程的開始時(shí),'工作'鏈接 將取提取。否則,"完成課程"鏈接 允許你從當(dāng)前課程的終點(diǎn)開始。

不多說了,我們開始吧。

Party Planner應(yīng)用程序結(jié)構(gòu)圖10

設(shè)置第一個(gè)Reducer

(Work Along | Completed Lesson)

Reducer是你應(yīng)用程序的基礎(chǔ)。隨著應(yīng)用程序緩存維護(hù)狀態(tài),reducer是動態(tài)調(diào)度時(shí)action和輸出新狀態(tài)表示的主要內(nèi)容。每個(gè)reducer應(yīng)該集中在與數(shù)據(jù)庫的表類似的特定部分或狀態(tài)片段上。

創(chuàng)建reducer是很簡單的一旦習(xí)慣了一個(gè)常見的成語,從不改變以前的狀態(tài),并總是返回一個(gè)新的狀態(tài)表示,當(dāng)相關(guān)的action被調(diào)度。如果 你是新來的store或Redux模式,這需要一些習(xí)慣來感覺自然。而不是使用諸如推送或重新分配先前存在的對象的變量的方法,而是依靠沒有改變的方法,如concat和Object.assign來返回新值。還困惑嗎?讓我們來看看個(gè)人在實(shí)踐中與其他的reducer有什么關(guān)系。

person的reducer需要處理5個(gè)action,移出一個(gè)person,向guest添加一個(gè)guest,從一個(gè)person中移出guest,并切換他們是否參加活動。為此, 我們將創(chuàng)建一個(gè)reducer函數(shù) ,接受以前的狀態(tài)和當(dāng)前 調(diào)度的action。然后,我們需要實(shí)現(xiàn)一個(gè)case語句,在執(zhí)行相關(guān)action時(shí)執(zhí)行正常的狀態(tài)重新計(jì)算。

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

//切記,要避免reducers內(nèi)部的直接改變
export const people = (state = [], action) => {
    switch(action.type){
      case ADD_PERSON:
        return [
            ...state,
          Object.assign({}, {id: action.payload.id, name: action.payload, guests: 0, attending: false})
        ];
      case REMOVE_PERSON:
        return state
            .filter(person => person.id !=== action.payload);
            //為了簡單語句,把更新放到dtails上
      case ADD_GUEST:
            return state.map(person => detail(person, action));
      case REMOVE_GUEST:
            return state.map(person => details(person, action));
      case TOGGLE_ATTENDING:
            return state.map(person => details(person, action));
       //當(dāng)沒有合適的action時(shí),始終會返回先前狀態(tài)的默認(rèn)值
      default:
            return state;
    }
}
配置Store中的actions

(Work Along | Completed Lesson)

在store應(yīng)用程序中修改state的唯一方式是通過dispatch。因此 ,action的記錄應(yīng)該是很清晰的,可讀的用戶交互歷史。action通常定義為字符串常量或封裝特定操作類型的服務(wù)上的靜態(tài)字符串值。在后一種情況下,提供功能以返回給定正常輸入的適當(dāng)動作。這些方法有助于標(biāo)準(zhǔn)化你的操作,同時(shí)提供額外的類型安全性,被稱之為action的創(chuàng)建者。

對于我們的就應(yīng)用程序,我們將每個(gè)應(yīng)用程序action導(dǎo)出字符 串常量。然后,這些將用于我們r(jià)educer案例語句的關(guān)鍵字以及每個(gè)派生action的類型。

初始化Actions
//Person 的Action常量
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 = 'TOOGLE_ATTENDING';

Components Container的使用

(Work Along | Completed Lesson)

在你的應(yīng)用程序中組件分為兩類,smart11dumb。那怎么區(qū)別哪個(gè)一個(gè)是start component,哪一個(gè)是dumb component?

SmartContainer 組件應(yīng)該作為你的首層組件,或路由組件 。這些組件通??梢灾苯釉L問store或?qū)С鼋M件。Smart Component組件通過服務(wù)或直接處理視圖event和action的dispatch。同時(shí),Smart Component組件還處理在同一視圖中從子組件發(fā)出的事件邏輯處理。

DumbChild組件通常僅作為顯示內(nèi)容,僅依賴于@Input參數(shù) ,以及合理的方式處理接收到的數(shù)據(jù) 。當(dāng)相關(guān)事件性在dump components時(shí),它們通過emit操作發(fā)射出來,交由smart父組件處理。你應(yīng)用程序中將大部分由dump組件構(gòu)成,因?yàn)樗麄兪仟?dú)立的,聚焦的,并且是可重復(fù)利用的。

part planner應(yīng)用程序需要一個(gè)container component。該組件將負(fù)責(zé)將適當(dāng)?shù)臓顟B(tài)轉(zhuǎn)交給每個(gè)子組件,并根據(jù)dumb component,person-input,person-list,以及以后的part-stats發(fā)出的事件進(jìn)行調(diào)度。

Container Component
@Component({
    selector: 'app',
    template: `
      <h3>@ngrx/store Party Planner</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
    ){
      /*
      示例沒有使用async管道,
      我們將在下一小節(jié)探索async管道
      */
      this.subscription = this._store
        .select('people')
        .subscribe(people => {
          this.people = people;
      });
    }
    //所有action狀態(tài)的改變都被轉(zhuǎn)發(fā)到reducer處理 
    addPerson(name)    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})
    }
    /*
    如果我沒有使用async管道而是手動創(chuàng)建訂閱(subscriptions),則需要在ngOnDestroy中取消訂閱(unsubscribe)
    */
    ngOnDestroy(){
      this.subscription.unsubscribe();
    }
}
Dumb Component - 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 {
    /*
      "dumb"組件接收input輸入和顯示數(shù)據(jù),發(fā)送相關(guān)事件到父組件 或container組件處理,除此之外什么也不做。
    */
    @Input() people;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}
Dumb Component - PersonInput
@Component({
    selector: 'person-input',
    template: `
      <input #personName type="text" />
      <button (click)="add(personName)">Add Person</button>
    `
})
export class PersonInput {
    @Output() addPerson = new EventEmitter();

    add(personInput){
      this.addPerson.emit(personInput.value);
      personInput.value = '';
    }
}
Smart Container11 和 Dumb Components

AsyncPipe管道的使用

(Work Along | Completed Lesson)

在Angular中AsyncPipe是一個(gè)獨(dú)特的,狀態(tài)化的管道,可以處理Observables和Promises。當(dāng)在具有Observables的模板表達(dá)式中使用AsyncPipe管道時(shí),使用了Observable可subscribe的功能,并且在視圖中顯示emit的值。該管道還可以自動取消訂閱,從而節(jié)省了在ngOnDestroy中手動取消訂閱的麻煩。在Store應(yīng)用程序中,你會發(fā)現(xiàn)在視圖組件中所有的組件都使用了AsyncPipe管道,且還很難知道發(fā)生了什么。有關(guān)AsyncPipe是如何工作的更詳細(xì)解釋,請查看我的文章了解和使用Angular的AsyncPipe或egghead.io上AsyncPipe的免費(fèi)視頻。

在我們的模板中使用AsyncPipe是很簡單的。你可以通過async操作符傳遞Observable(或Promise),并創(chuàng)建訂閱對象,通過async管道符解析的數(shù)據(jù)更新模板。因?yàn)槲覀兪褂昧薃syncPipe管道,我們還可以從組件的構(gòu)造函數(shù)和生命周期鉤子函數(shù) ngOnDestroy中手動取消訂閱。現(xiàn)在我們使用AsyncPipe默認(rèn)自動處理。

重構(gòu)為AsyncPipe管道
@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管道,這將會被自動訂閱,使用新解析的數(shù)據(jù)顯示在我們的模板上。
        當(dāng)組件銷毀時(shí),會自動解除訂閱
      */
      this.people = _store.select('people');
    }
    //所有狀態(tài)的改變都會通過dispatch轉(zhuǎn)發(fā)與處理
    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中不再需要手動取消訂閱
}
使用ChangeDetection.OnPush

(Work Along | Completed Lesson)

在Angular中使利用中央狀態(tài)樹,可以帶來可預(yù)測性和可維護(hù)性,同時(shí)還有提升性能。為了體現(xiàn)這一性能優(yōu)勢,我們可以使用ChangeDetection.OnPush。

OnPush背景的概念很簡單,當(dāng)組件僅依賴于輸入,而這些輸入的引用沒有發(fā)生改變,Angular就可以跳過組件的變化檢測。如前所述,所有state的委托應(yīng)該在smart component或頂級組件中處理。這使我們的應(yīng)用程序大多數(shù)組件完全依賴于輸入,安全地允許我們在組件 中將ChangeDetectionStrategy設(shè)置為OnPush。組件可以跳過組件的變化檢測,直到必要時(shí),它將會給我們免費(fèi)提升性能。

使用OnPush更改組件的變化檢測,我們需要在@Component裝飾器中修改changeDetection屬性設(shè)置為ChangeDetection.OnPUsh。,如此,現(xiàn)在Angular將會跳過這些組件和子組件的變化檢測,直到輸入的引用改變。

更新為ChangeDetection.OnPush
@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改變變化檢測,使得組件僅依賴于輸入引用的變化 ,這將大大提升應(yīng)用程序的性能。
*/
export class PersonList {
    /*
     "dumb"組件基于輸入顯示數(shù)據(jù)和發(fā)射相關(guān)的事件給父組件或"container"組件處理,除此之外沒有別的。
    */
    @Input() people;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}
State擴(kuò)展

(Work Along | Completed Lesson)

多數(shù)Store應(yīng)用程序都是由多個(gè)reducer組成的,每個(gè)reducer都可以管理自己的狀態(tài)。對于這個(gè)示例,我們有兩個(gè)reducer,一個(gè)用于管理part attendees,另一個(gè)應(yīng)用于列表當(dāng)前 action的過濾器。我們首先定義action的常量,指定用戶應(yīng)用可以應(yīng)用的過濾器。

我們先創(chuàng)建partFilter的reducer。對于此我們有幾個(gè)選項(xiàng)。首先是簡的返回filter的過濾器字符串。然后,我們可以編寫一個(gè)基于當(dāng)前action的過濾器過濾列表的服務(wù)或組件中的方法。雖然這個(gè)做還不錯(cuò),但是根據(jù)當(dāng)前的過濾器狀態(tài)返回應(yīng)用于part list的功能是更可擴(kuò)展的。在將來,添加更多的過濾器就像創(chuàng)建一個(gè)新的case語句一樣適當(dāng)簡單的返回對應(yīng)的投影處理函數(shù)。

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

//根據(jù)所選過濾器然后返回對應(yīng)的結(jié)果
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;
    }
};
Part Filter Actions
//Party Filter常量
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_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>();
}
視圖中的狀態(tài)片段

(Work Along | Completed Lesson)

@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

}
@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;
    //for now, we will pass filter down and apply
    @Input() filter;
    @Output() addGuest = new EventEmitter();
    @Output() removeGuest = new EventEmitter();
    @Output() removePerson = new EventEmitter();
    @Output() toggleAttending = new EventEmitter();
}
//timerOne emits first value at 1s, then once every 4s
const timerOne = Rx.Observable.timer(1000, 4000);
//timerTwo emits first value at 2s, then once every 4s
const timerTwo = Rx.Observable.timer(2000, 4000)
//timerThree emits first value at 3s, then once every 4s
const timerThree = Rx.Observable.timer(3000, 4000)

//when one timer emits, emit the latest values from each timer as an array
const combined = Rx.Observable
.combineLatest(
    timerOne,
    timerTwo,
    timerThree
);

const subscribe = combined.subscribe(latestValues => {
    //grab latest emitted values for timers one, two, and three
    const [timerValOne, timerValTwo, timerValThree] = latestValues;
  /*
      Example:
    timerOne first tick: 'Timer One Latest: 1, Timer Two Latest:0, Timer Three Latest: 0
    timerTwo first tick: 'Timer One Latest: 1, Timer Two Latest:1, Timer Three Latest: 0
    timerThree first tick: 'Timer One Latest: 1, Timer Two Latest:1, Timer Three Latest: 1
  */
  console.log(
    `Timer One Latest: ${timerValOne}, 
     Timer Two Latest: ${timerValTwo}, 
     Timer Three Latest: ${timerValThree}`
   );
});

//combineLatest also takes an optional projection function
const combinedProject = Rx.Observable
.combineLatest(
    timerOne,
    timerTwo,
    timerThree,
    (one, two, three) => {
      return `Timer One (Proj) Latest: ${one}, 
              Timer Two (Proj) Latest: ${two}, 
              Timer Three (Proj) Latest: ${three}`
    }
);
//log values
const subscribe = combinedProject.subscribe(latestValuesProject => console.log(latestValuesProject));
//Create an observable that emits a value every second
const myInterval = Rx.Observable.interval(1000);
//Create an observable that emits immediately, then every 5 seconds
const myTimer = Rx.Observable.timer(0, 5000);
//Every time interval emits, also get latest from timer and add the two values
const latest = myInterval
  .withLatestFrom(myTimer)
  .map(([interval, timer]) => {
    console.log(`Latest Interval: ${interval}`);
    console.log(`Latest Timer: ${timer}`);
    return interval + timer;
  });
//log total
const subscribe = latest.subscribe(val => console.log(`Total: ${val}`));
@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
}
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;
    });
};
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>
}
//pre middleware takes an observable of actions, returning an observable
export const actionLogger = action => {
  return action.do(a => console.log('DISPATCHED ACTION:', a));
}
//post middleware takes an observable of state, returning observable
export const stateLogger = state => {
  return state.do(s => console.log('NEW STATE:', s));
}
bootstrap(App, [
  provideStore({people, partyFilter}),
  usePreMiddleware(actionLogger),
  usePostMiddleware(stateLogger)
]);
import {Injectable} from 'angular2/core';
//simple service wrapping local storage
@Injectable()
export class LocalStorageService {
    setItem(key, value){
      localStorage.setItem(key, JSON.stringify(value));
    }
    
    getItem(key){
      return JSON.parse(localStorage.getItem(key));
    }
}
/*
  create middleware with a dependency on the localStorageService
  basic example, accept state key to sync with local storage
*/
export const localStorageMiddleware = key => createMiddleware(localStorageService => {
  return state => {
    //sync specified state slice with local storage
    return state.do(state => localStorageService.setItem(key, state[key]));
  }
}, [LocalStorageService]);
import {provide, Provider} from 'angular2/core';
import {INITIAL_STATE} from '@ngrx/store';

export const rehydrateState = key => {
  //override initial state token for use in store
  return provide(INITIAL_STATE, { 
    useValue: { [key]: JSON.parse(localStorage.getItem(key))}; 
  });
};
bootstrap(App, [
  LocalStorageService,
  provideStore({people, partyFilter}),
  usePreMiddleware(actionLogger),
  usePostMiddleware(stateLogger, localStorageMiddleware('people')),
  rehydrateState('people')
]);
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(App, [
  //wrap people in reset meta-reducer
  provideStore({people: reset(people), partyFilter})
]);
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;
  };
}

天之驕子 2017.8.20 深圳

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多