我們的 Store 內部大致完成,但是對於客戶 (Subscriber),我們還少了介面,在 ngrx/store 裡,給客戶的介面是用 store.select(),.select() 最主要的目的是提供 Subscriber 它所要的狀態,而不是整個狀態樹,它是一個 Observable,客戶 subscribe() 後會被 push 新的狀態。
加入 Select
我們知道 Store 是一個 BehaviorSubject,它隨時會紀錄最新的狀態樹,而狀態樹用 key 來做分類,所以做 select() 其實很簡單,只要用一個 .map() Operator如下:
class Store extends Rx.BehaviorSubject<Action> {
// 省略 …
select(key:string) {
return this.map(state=> state[key]);
}
}
// 省略 …
// add subscriber
const sub1 = store.select(‘login’)
.subscribe(s => console.log(‘login status => ‘, s));
const sub2 = store.select(‘messages’)
.subscribe(s => console.log(‘messages => ‘, s));
// start dispatch action about login
store.dispatch({type: ‘LOGIN’});
store.dispatch({type: ‘LOGOUT’});
store.dispatch({type: ‘LOGIN’});
// start dispatch about messages
store.dispatch({type: ‘ADD_MESSAGE’, payload: {id: 1, msg: ‘First Message’}});
store.dispatch({type: ‘ADD_MESSAGE’, payload: {id: 2, msg: ‘Second Message’}});
store.dispatch({type: ‘REMOVE_MESSAGE’, payload: 1});
這裡假設 sub1只對 login 有興趣,而 sub2 只對 messages 有興趣,但是我們來看一下輸出
在 Observable 中有一個 Operator .distinctUntilChange(),它會根據新的 .next() 做過濾,如果值不變,它會將它過濾掉,不會丟給 Subscriber。
Select with distinctUntiChange
我們加入 .distinctUntilChange() 在 .select()
class Store extends Rx.BehaviorSubject<Action> {
// 省略 …
select(key:string) {
return this.map(state=> state[key])
.distinctUntilChanged();
}
}
結果如下
完整程式
interface Action {
type: string;
payload?: any
}
class Dispatcher extends Rx.Subject<Action> {
dispatch(act) {
this.next(act);
}
}
class Store extends Rx.BehaviorSubject<Action> {
constructor(private dispatcher, private reducer, initialState) {
super(initialState);
this.dispatcher
.do((v) => { /*console.log(‘do some effect for’, v.type) */ })
.scan((s, v) => this.reducer(s, v), initialState)
.subscribe(state => {
super.next(state); // new state, push to subscriber
});
}
dispatch(act) { // delegate to dispatcher
this.dispatcher.dispatch(act);
}
// override next to allow store subscribe action$
next(act) {
this.dispatcher.dispatch(act);
}
select(key:string) {
return this.map(state=> state[key])
.distinctUntilChanged();
}
}
const login = (state = false, action) => {
switch (action.type) {
case ‘LOGIN’:
return true;
case ‘LOGOUT’:
return false;
default:
return state;
}
}
const messages = (state = [], action) => {
switch (action.type) {
case ‘ADD_MESSAGE’:
return […state, action.payload];
case ‘REMOVE_MESSAGE’:
return state.filter(msg => msg.id
!= action.payload);
default:
return state;
}
}
const myReducer = {login, messages}; // object of two reducer functions
const combineReducer = reducers => (state, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key]=reducers[key](state[key], action);
//console.log(‘nextState is ‘, nextState);
return nextState;
}, {})
}
const rootReducer = combineReducer(myReducer);
// instanciate new store with initialstate
const initialState = {login: false, messages: []};
const dispatcher = new Dispatcher();
const store = new Store(dispatcher, rootReducer, initialState);
// add subscriber
const sub1 = store.select(‘login’)
.subscribe(s => console.log(‘login status => ‘, s));
const sub2 = store.select(‘messages’)
.subscribe(s => console.log(‘messages => ‘, s));
// start dispatch action about login
store.dispatch({type: ‘LOGIN’});
store.dispatch({type: ‘LOGOUT’});
store.dispatch({type: ‘LOGIN’});
// start dispatch about messages
store.dispatch({type: ‘ADD_MESSAGE’, payload: {id: 1, msg: ‘First Message’}});
store.dispatch({type: ‘ADD_MESSAGE’, payload: {id: 2, msg: ‘Second Message’}});
store.dispatch({type: ‘REMOVE_MESSAGE’, payload: 1});
codepen
至此,我們已經將 Observable 跟 store 介紹差不多了, ngrx/store 有三大主軸, Angular (ng), Observable (rx) 跟 Store (flux),接下來我們要進入 Angular 主題來應用 ngrx/store。
转载请注明:XAMPP中文组官网 » Store 加入 select