單一數據源 Single source of truth:整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。redux
state 只讀 State is read-only:唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。這樣確保了視圖和網絡請求都不能直接修改 state,相反它們只能表達想要修改的意圖。數組
使用純函數來執行修改 Changes are made with pure functions:爲了描述 action 如何改變 state tree ,你須要編寫 reducers。Reducer 只是一些純函數,它接收先前的 state 和 action,並返回新的 state。網絡
Action: 是把數據傳到 store 的惟一手段,表達修改 state 的意圖。它是 store 數據的惟一來源。通常經過 store.dispatch() 將 action 傳到 store。session
{ type:types.RECEIVE_CASES, cases }
Action Creator:是一個建立 action 的純函數。閉包
export function receiveCases(cases) { return { type:types.RECEIVE_CASES, cases } }
Reducer:是一個純函數,接收舊的 state 和 action,一般包含了switch結構,根據dispatch傳來的state和type來生成新的state。app
Store:構建順序:框架
reducers => 'combineReducers' => rootReducer => 'createStore' (+ 'applyMiddleware') | ===> **Store**
redux.createStore(reducer, initialState) 傳入了reducer、initialState,並返回一個store對象。ide
store對象對外暴露了dispatch、getState、subscribe方法函數
store對象經過getState() 獲取內部狀態ui
initialState爲 store 的初始狀態,若是不傳則爲undefined
store對象經過reducer來修改內部狀態
store對象建立的時候,內部會主動調用dispatch({ type: ActionTypes.INIT });來對內部狀態進行初始化。經過斷點或者日誌打印就能夠看到,store對象建立的同時,reducer就會被調用進行初始化。
/** * Creates a Redux store that holds the state tree. * The only way to change the data in the store is to call `dispatch()` on it. * * There should only be a single store in your app. To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`. * * @param {Function} reducer A function that returns the next state tree, given * the current state tree and the action to handle. * --- reducer 是 Function,用於構建 state 樹 * * @param {any} [preloadedState] The initial state. You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session. * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * --- preloadedState 初始化 state 樹 * * @param {Function} enhancer The store enhancer. You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux * is `applyMiddleware()`. * --- enhancer 至關於 AOP 插件 * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. * --- 最終返回 Store,能獲取 state、分發action、訂閱變化 */ export default function createStore(reducer, preloadedState, enhancer) { ... // --- 校驗參數,及執行 enhancer var currentReducer = reducer var currentState = preloadedState // --- state 樹 var currentListeners = [] // --- 註冊的監聽器列表,實時處理 dispatch 事件 var nextListeners = currentListeners // --- 註冊的監聽器列表,實時接收 subscribe 事件 var isDispatching = false // --- 若是 reducer 正在執行,會拋出異常 // --- 將 nextListeners 作爲 currentListeners 的副本 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // --- 返回 state function getState() { ... // --- 校驗 return currentState } // --- 註冊監聽器 // 很常見的監聽函數添加方式,當store.dispatch 的時候被調用 // store.subscribe(listener) 返回一個方法(unscribe),能夠用來取消監聽 function subscribe(listener) { listeners.push(listener); var isSubscribed = true; return function unsubscribe() { if (!isSubscribed) { return; } isSubscribed = false; var index = listeners.indexOf(listener); listeners.splice(index, 1); }; } // --- 分發 action,修改 state 的惟一方式 function dispatch(action) { // 如下狀況會報錯 // 1. 傳入的action不是一個對象 // 2. 傳入的action是個對象,可是action.type 是undefined try { isDispatching = true // 就是這一句啦, 將 currentState 設置爲 reducer(currentState, action) 返回的值 currentState = currentReducer(currentState, action) // 執行 reducer,state 被更新 } finally { isDispatching = false } // --- 若是有監聽函數,就順序調用監聽器方法 //listeners.slice().forEach(listener => listener()); var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { listeners[i]() } // --- 返回結果仍是 action return action } // --- 替換 reducer,用於熱替換、按需加載等場景 function replaceReducer(nextReducer) { ... // --- 校驗 currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) } function observable() { // --- 略 } // --- 建立時初始化應用狀態 // redux.createStore(reducer, initialState) 的時候, 內部會 本身調用 dispatch({ type: ActionTypes.INIT }); // 來完成state的初始化 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
function TodoReducer(state, action) {} function FilterReducer(state, action) {} var finalReducers = redux.combineReducers({ todos: TodoReducer, filter: FilterReducer });
redux.combineReducers(reducerMap) 用來合成多個reducer,其實就是把全部輸入的reducer閉包封裝,返回一個combination函數,這個函數會接受state和action,依次調用封裝的reducer,分發state和action,來生成新的state
combineReducers(reducerMap) 傳入一個對象,並返回一個全新的reducer。調用方式跟跟普通的reducer同樣,也是傳入state、action。
經過combineReducers,對 store 的狀態state進行拆分,
reducerMap的key,就是 state 的key,而 調用對應的reducer返回的值,則是這個key對應的值。如上面的例子,state.todos == TodoReducer(state, action)
redux.createStore(finalReducers, initialState) 調用時,一樣會對 state 進行初始化。這個初始化跟經過普通的reducer進行初始化沒多大區別。舉例來講,若是 initialState.todos = undefined,那麼 TodoReducer(state, action) 初始傳入的state就是undefined;若是initialState.todos = [],那麼 TodoReducer(state, action) 初始傳入的state就是[];
store.dispatch(action),finalReducers 裏面,會遍歷整個reducerMap,依次調用每一個reducer,並將每一個reducer返回的子state賦給state對應的key。
/** * Turns an object whose values are different reducer functions, into a single * reducer function. It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions. * * @param {Object} reducers An object whose values correspond to different * reducer functions that need to be combined into one. One handy way to obtain * it is to use ES6 `import * as reducers` syntax. The reducers may never return * undefined for any action. Instead, they should return their initial state * if the state passed to them was undefined, and the current state for any * unrecognized action. * * @returns {Function} A reducer function that invokes every reducer inside the * passed object, and builds a state object with the same shape. */ export default function combineReducers(reducers) { // --- 過濾 reducer var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) // --- 返回 combination 函數 return function combination(state = {}, action) { // --- 校驗 // --- 根據 reducer key 及執行結果構造 state 樹 var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) // --- 執行 reducer // --- 校驗 nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
applyMiddleware 傳入 middleware 鏈,並返回應用這些 middleware 的 store enhancer
export default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { // 內部先建立一個store (至關於直接調用 Redux.createStore(reducer, initialState)) var store = next(reducer, initialState); // 保存最初始的store.dispatch var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, // 最後面, dispatch 被覆蓋, 變成包裝後的 dispatch 方法 dispatch: (action) => dispatch(action) }; // 返回一個數組 // 貼個例子在這裏作參考,redux-thunk // function thunkMiddleware(store) { // var dispatch = store.dispatch; // var getState = store.getState; // // 這裏的next其實就是dispatch // return function (next) { // return function (action) { // return typeof action === 'function' ? action(dispatch, getState) : next(action); // }; // }; //} /* chain 是個數組, 參考上面的 middlleware (redux-thunk),能夠看到,chain的每一個元素爲以下形式的function 而且, 傳入的 store.getState 爲原始的 store.getState,而 dispatch則是包裝後的 dispatch(不是原始的store.dispatch) 彷佛是爲了確保, 在每一個middleware裏調用 dispatch(action), 最終都是 用原始的 store.dispatch(action) 避免 store.dispatch 被覆蓋, 致使middleware 順序調用的過程當中, store.dispatch的值變化 --> store.dispatch 返回的值可能會有不一樣 違背 redux 的設計理念 這裏的 next 則爲 原始的 store.dispatch (見下面 compose(...chain)(store.dispatch) ) function (next) { return function (action) { } } */ chain = middlewares.map(middleware => middleware(middlewareAPI)); // compose(...chain)(store.dispatch) 返回了一個function // 僞代碼以下, // function (action) { // middleware(store)(store.dispatch); // } dispatch = compose(...chain)(store.dispatch); // 從右到左, middleware1( middleware2( middleware3(dispatch) ) ) // 因而,最終調用 applyMiddleware(...middlewares)(Redux.createStore) // 返回的 store, getState,subscribe 方法都是原始的那個 store.getState, store.subscribe // 至於dispatch是封裝過的 return { ...store, dispatch }; }; }
export default function applyMiddleware(...middlewares) { // --- 傳入 createStore,返回加強版的 store return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] // --- middleware 中的參數 var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // --- 給 middleware 傳參 chain = middlewares.map(middleware => middleware(middlewareAPI)) // --- 組合 chain,傳入原始的 dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, // --- 保留 subscribe, getState, replaceReducer, [$$observable]: observable dispatch // --- 用新 dispatch 覆蓋,以後調用 dispatch 就會觸發 chain 內的 middleware 鏈式執行 } } }
React-Redux是用在鏈接React和Redux上的。若是你想同時用這兩個框架,那麼React-Redux基本就是必須的了。
export default class Provider extends Component { getChildContext() { // 將其聲明爲 context 的屬性之一 return { store: this.store } } constructor(props, context) { super(props, context) // 接收 redux 的 store 做爲 props this.store = props.store } render() { return Children.only(this.props.children) } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired } Provider.childContextTypes = { store: storeShape.isRequired }