現實的狀態跟 Reducer 通常都有幾個以上,我們來看一下多個 Reducer 的例子
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
承前面的例子我們再加上一個簡單的 login
reducer, 這個 reducer 狀態很單純,是一個布林值 (boolean) 表示使用者登入狀態,這時候的 myReducer 是一個物件,它的內容 key 是 login
跟 messages
,value 是兩個函數 ((state, action) => state),而狀態樹會是
inteface State {
login: boolean;
messages: message[];
}
interface message {
id: number;
msg: string;
}
我們用下列的程式來應用這個多重的 Reducer 到這個多重的狀態樹
const combineReducer = reducers => (state, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key]=reducers[key](state[key], action);
return nextState;
}, {})
}
const rootReducer = combineReducer(myReducer);
稍微說明一下
login
跟 message
在這個例子中。.reduce()
這個 Operator, 其實可以將它當成 for-loop
,差別在於他會累積結果,也就是累積新的狀態,initial 從 {}
開始。login
, 所以它會取 state[key] 也就是 login
的狀態,加上 action應用於 reducers[key] 就是 login
reducer 的函數 ((state, action) => state),產生新的 login
nextState。.reduce()
將這個新的狀態加入 {}
。message
, 會用 state[key] 也就是 messages
的舊狀態加入 action 應用 reducer[key] 就是 messages
reducer 的函數 ((state, action) => state), 產生 messages
新的狀態,.reduce()
將這個新的狀態加入上面的物件。.reduce()
將新生的狀態一個一個加入 {}
,產生新的狀態樹。看到這裡,大家大概明瞭為什麼叫它 Reducer 了,在一個更複雜的狀態樹中,Reducer 負責將一個個狀態應用到對應的函數中,再將它們包成一個狀態樹
有了 Reducer, 我們在將它加入 Store 的架構中,就成了完整的 Store
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);
}
}
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
const initialState = {login: false, messages: []};
const dispatcher = new Dispatcher();
const store = new Store(dispatcher, rootReducer, initialState);
// add subscriber
const sub1 = store.subscribe(s => console.log('state ===> ', s));
// start dispatch action
store.dispatch({type: 'LOGIN'}); // dispatch new action to store
store.dispatch({type: 'LOGOUT'}); // dispatch another action
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});
部分輸出如下
有了完整的 Reducer, 接下來我們來看 store.select()
要怎麼做?