Redux初學理解
一個狀態(State)管理器,集中管理應用中所有組件的狀態。
所有組件的狀態保存在一個對象裏。
Redux主要用途?
1)簡化組件依賴關系,使可以共享或改變所有組件的狀態。
2)狀態的改變,會引起視圖的改變,所以如果想改變視圖(View),不管涉及誰的改變,統一找Redux就行了,因為狀態都在他那裏集中管理。
3)Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麽樣,反之亦然。
如何使用?
1)創建唯一的一個store:
import { createStore } from ‘redux‘; const store = createStore(fn);
2)獲取state
const state = store.getState();
3)改變state,需要發送Action通知。
Action 就是 View 發出的通知,表示 State 應該要發生變化了。
發送通知的方法是store.dispatch(action)。如下:
import { createStore } from ‘redux‘; const store = createStore(fn); store.dispatch({ type: ‘ADD_TODO‘, payload: ‘Learn Redux‘ });
Actiond的結構:
const action = {
type: ‘ADD_TODO‘,
payload: ‘Learn Redux‘
};
type是必須的,用於識別一個action,其他是可選的。
可以寫一些輔助函數來創建action,避免dispatch看著太復雜。
4)Reducer
Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函數,它接受 Action 和當前 State 作為參數,返回一個新的 State。
這個函數,是在store創建是傳入的,就是上面創建描述的:const store = createStore(fn),也就是:
const store = createStore(reducer)
以後每當store.dispatch發送過來一個新的 Action,就會自動調用 Reducer,得到新的 State。
Reducer 函數最重要的特征是,它是一個純函數。也就是說,只要是同樣的(類型)輸入,必定得到同樣的(類型)輸出。
純函數是函數式編程的概念,必須遵守以下一些約束。
不得改寫參數
不能調用系統 I/O 的API
不能調用Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果
由於 Reducer 是純函數,就可以保證同樣的State,必定得到同樣的 View。但也正因為這一點,Reducer 函數裏面不能改變 State,必須返回一個全新的對象。
5)store.subscribe()
Store 允許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。
import { createStore } from ‘redux‘;
const store = createStore(reducer);
store.subscribe(listener);
顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。
store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
Store 的實現
以上介紹了 Redux 涉及的基本概念,可以發現 Store 提供了三個方法。
store.getState()
store.dispatch()
store.subscribe()
import { createStore } from ‘redux‘;
let { subscribe, dispatch, getState } = createStore(reducer);
createStore方法還可以接受第二個參數,表示 State 的最初狀態。這通常是服務器給出的。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
上面代碼中,window.STATE_FROM_SERVER就是整個應用的狀態初始值。註意,如果提供了這個參數,它會覆蓋 Reducer 函數的默認初始值。
下面是createStore方法的一個簡單實現,可以了解一下 Store 是怎麽生成的。(以下代碼僅僅用於理解原理,屬於進階教程)
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
dispatch({});
return { getState, dispatch, subscribe };
};
Reducer 的拆分
Reducer 函數負責生成 State。由於整個應用只有一個 State 對象,包含所有數據,對於大型應用來說,這個 State 必然十分龐大,導致 Reducer 函數也十分龐大。
請看下面的例子。
const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action; //解包
switch (type) {
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}
};
上面代碼中,三種 Action 分別改變 State 的三個屬性。
ADD_CHAT:chatLog屬性
CHANGE_STATUS:statusMessage屬性
CHANGE_USERNAME:userName屬性
這三個屬性之間沒有聯系,這提示我們可以把 Reducer 函數拆分。不同的函數負責處理不同屬性,最終把它們合並成一個大的 Reducer 即可。
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};
上面代碼中,Reducer 函數被拆成了三個小函數,每一個負責生成對應的屬性。
這樣一拆,Reducer 就易讀易寫多了。而且,這種拆分與 React 應用的結構相吻合:一個 React 根組件由很多子組件構成。這就是說,子組件與子 Reducer 完全可以對應。
Redux 提供了一個combineReducers方法,用於 Reducer 的拆分。你只要定義各個子 Reducer 函數,然後用這個方法,將它們合成一個大的 Reducer。
import { combineReducers } from ‘redux‘;
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
export default todoApp;
上面的代碼通過combineReducers方法將三個子 Reducer 合並成一個大的函數。
這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。如果不同名,就要采用下面的寫法。
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
// 等同於
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
總之,combineReducers()做的就是產生一個整體的 Reducer 函數。該函數根據 State 的 key 去執行相應的子 Reducer,並將返回結果合並成一個大的 State 對象。
下面是combineReducer的簡單實現(實現,告訴你原理,所以然)。
const combineReducers = reducers => {
return (state = {}, action) => {
return Object.keys(reducers).reduce(
(nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
},
{}
);
};
};
你可以把所有子 Reducer 放在一個文件裏面,然後統一引入。
import { combineReducers } from ‘redux‘
import * as reducers from ‘./reducers‘
const reducer = combineReducers(reducers)
工作流程
本節對 Redux 的工作流程,做一個梳理。
首先,用戶發出 Action。
store.dispatch(action);
然後,Store 自動調用 Reducer,並且傳入兩個參數:當前 State 和收到的 Action。 Reducer 會返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有變化,Store 就會調用監聽函數。
// 設置監聽函數
store.subscribe(listener);
listener可以通過store.getState()得到當前狀態。如果使用的是 React,這時可以觸發重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
Redux初學理解