Redux的設計思想很簡單,一、web應用是一個狀態機,視圖與狀態一一對應;二、全部狀態保存在一個對象裏
複製代碼
Store 應用狀態的管理者,能夠看做一個數據庫,包含如下函數web
const store = {
dispatch(action), // 觸發state改變的惟一方法
subscribe(listener), // 訂閱函數,store變化觸發的訂閱的監聽函數,返回取消訂閱的方法
getState(), // 獲取state的方法
replaceReducer(nextReducer), // 替換reducer
[$$observable]: observable
}
複製代碼
一個應用只應有一個單一的 store,其管理着惟一的應用狀態 state Redux提供createStore函數,用於生成store數據庫
import { createStore } from 'redux';
const store = createStore(reducer);
複製代碼
statejson
一個對象,包含全部數據,能夠看做數據庫中的數據,經過const state = store.getState()
獲取redux
Actionapi
一個包含type屬性的對象,做用是描述如何修改state,它會運輸數據到store,做爲reducer函數的參數數組
Action Creatorbash
生成Action的函數app
dispatch異步
view發出Action的惟一方法,redux規定不能直接在業務代碼中修改state,若想要修改,只能經過store.dispatch(Action)實現async
Reducer
一個純函數,會根據Acion的type值生成新的state替換調原來調state
只對關鍵代碼進行解讀。
index.ts
先看入口文件
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
//...
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
複製代碼
很簡單,導出一個對象,接下來一個個分析
createStore.ts
import $$observable from './utils/symbol-observable'
import {
Store,
PreloadedState,
StoreEnhancer,
Dispatch,
Observer,
ExtendState
} from './types/store'
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore (
reducer, // 由於dispatch會自動觸發reducer的執行,因此在生成store的時候須要把reducer函數傳遞進來
preloadedState?, // 初始state
enhancer? // 加強,經過一系列中間件加強dispatch函數,例如對於一步Action對處理,後面詳細介紹
) {
// ...對參數的判斷
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
let currentReducer = reducer // reducer函數
let currentState = preloadedState // 當前state
let currentListeners = [] // 用於存儲訂閱的回調函數,dispatch 後逐個執行
let nextListeners = currentListeners // 下一個事件循環的回調函數數組
let isDispatching = false // 是否正在dispatch
// 判斷當前的回調函數隊列是否與下一個回調函數隊列相同,不能影響本次的執行
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 獲取state
function getState() {
// 直接返回state
return currentState
}
// 訂閱--取消訂閱
function subscribe(listener) {
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
// 取消訂閱
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
// 將取消訂閱的回調函數從數組中移除
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// 派發一個Action用於改變state
// 若是dispatch的不是一個對象類型的action(同步),而是一個Promise/Thunk(異步),
// 就須要引入redux-thunk 等中間件來反轉控制權,具體會在applyMiddlewares()方法中解析
function dispatch(action) {
// 確保是一個普通的對象,若非普通對象,原型鏈上會出現其餘屬性
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// 確保擁有type屬性
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 是否正在dispatch
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
// dispatch 觸發reducer函數,返回state,賦值給currentState,這也是爲何咱們要在建立store對象的時候傳入reducer
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 逐個執行訂閱的回調函數
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 替換當前的reducer
function replaceReducer (
nextReducer
) {
// 直接替換,簡單粗暴
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
return store
}
// 這是留給 可觀察/響應式庫 的接口,具體實現能夠自行搜索一下
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
const observerAsObserver = observer
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// 初始化state
dispatch({ type: ActionTypes.INIT })
// 返回store對象
const store = {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
return store
}
複製代碼
createStore(reducer, preloadState?, enhancer?)是Redux的核心代碼,它給咱們提供了獲取state的方法getState()、觸發state改變的方法dispatch(Action)等
combineReducers.ts
import { Reducer } from './types/reducers'
import { AnyAction, Action } from './types/actions'
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
import {
ReducersMapObject,
StateFromReducersMapObject,
ActionFromReducersMapObject
} from './types/reducers'
import { CombinedState } from './types/store'
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
}
// 全部reducer的key集合
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// 返回合成後的 reducer
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] // 獲取當前子的state
const nextStateForKey = reducer(previousStateForKey, action) // 執行起對應的reducer
nextState[key] = nextStateForKey // 將子nextState掛載到對應的鍵名
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state // 返回總的state
}
}
複製代碼
假如咱們的應用有兩個狀態須要管理,一個是打印日誌,另外一個是計數器功能,咱們能夠把它們寫在一個reducer中,但若是還有其餘動做,這個堆在一個會讓代碼變得難以維護,因此最好但辦法是拆分reducer,使每個動做變得單一,最後在整合在一塊兒,combineReducers()就是爲此而生的。須要注意的是,子reducer的名稱應該與起對應的state相同。
好比咱們的目錄結構以下
reducers/
├── index.js
├── counterReducer.js
├── todosReducer.js
複製代碼
就能夠這樣實現
/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'
const rootReducer = combineReducers({
counter: counterReducer, // 鍵名就是該 reducer 對應管理的 state
todos: todosReducer
})
export default rootReducer
-------------------------------------------------
/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 傳入的 state 實際上是 state.counter
switch (action.type) {
case 'INCREMENT':
return counter + 1 // counter 是值傳遞,所以能夠直接返回一個值
default:
return counter
}
}
-------------------------------------------------
/* reducers/todosReducers */
export default function todosReducer(todos = [], action) { // 傳入的 state 實際上是 state.todos
switch (action.type) {
case 'ADD_TODO':
return [ ...todos, action.payload ]
default:
return todos
}
}
複製代碼
這樣作的好處不言而喻。不管您的應用狀態樹有多麼的複雜,均可以經過逐層下分管理對應部分的 state:
counterReducer(counter, action) -------------------- counter
↗ ↘
rootReducer(state, action) —→∑ ↗ optTimeReducer(optTime, action) ------ optTime ↘ nextState
↘—→∑ todo ↗
↘ todoListReducer(todoList,action) ----- todoList ↗
注:左側表示 dispatch 分發流,∑ 表示 combineReducers;右側表示各實體 reducer 的返回值,最後彙總整合成 nextState
複製代碼
import { Dispatch } from './types/store'
import {
AnyAction,
ActionCreator,
ActionCreatorsMapObject
} from './types/actions'
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function (this: any, ...args: any[]) {
// 給Action Creator 加上dispatch技能,也就是包裹一下
return dispatch(actionCreator.apply(this, args))
}
}
export default function bindActionCreators(
actionCreators,
dispatch
) {
// 處理actionCreators是函數的狀況
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? 'null' : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 處理actionCreators是對象的狀況
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
複製代碼
bindActionCreators(actionCreators, dispatch)這個函數的功能也比較單一,就是dispatch(ActionCreator(XXX))
,這個方法在異步狀況下,沒什麼卵用。。。
export default function compose(...funcs) =>
// 複合函數 compose(func1, func2, func3)(0) => func3(func2(func1(0)))
export default function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
複製代碼
這個函數很是簡單,功能也很單一,主要用了數組的reduce方法,將函數做爲reduce的回調函數的參數,參考文檔
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
複製代碼
中間件就是一個函數,對dispatch進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其餘功能。
它是 Redux 的原生方法,做用是將全部中間件組成一個數組,依次執行。目的是爲了在dispatch先後,統一處理想作對事。
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'
export default function applyMiddleware(
...middlewares
) {
return (createStore) => (
reducer,
...args
) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
}
// 中間件增長api
const middlewareAPI = {
getState,
dispatch
}
// chain存放中間件的數組,逐個增長api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose複合函數 a(b(c(0))) => compose(a, b, c)(0)
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
從源碼能夠看出,這個函數的主要做用就是經過各類中間件對dispatch進行加強,藉此咱們就能夠處理異步Action等問題了。
咱們能夠思考一下爲何處理異步Action就須要對dispatch進行加強,緣由以下:
(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其餘功能,也承擔不了,由於理論上,純函數不能進行讀寫操做。
(2)View:與 State 一一對應,能夠看做 State 的視覺層,也不合適承擔其餘功能。
(3)Action:存放數據的對象,即消息的載體,只能被別人操做,本身不能進行任何操做。
因此只有發送 Action 的這個步驟,即store.dispatch()方法,能夠添加功能。
同步操做只須要發出一種Action,異步操做則須要發出3種,發送時、成功時、失敗時,例如:
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
複製代碼
一樣的state也須要進行對應的改造
let state = {
// ...
isFetching: true, // 是否正在抓去數據
didInvalidate: true, // 數據是否過期
lastUpdated: 'xxxxxxx' // 更新數據時間
}
複製代碼
因此異步操做的思路應該是:
前面說處處理異步Action須要藉助到redux-thunk等中間件,如今具體分析一下redux-thunk是怎麼處理異步請求的。
異步操做至少要送出兩個 Action:用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;如何才能在操做結束時,系統自動送出第二個 Action 呢?
最主要的就是在Action Creator中處理,例如store.dispatch(fetchPosts(selectedPost))
fetchPosts()就是一個Action Creator,看一下內部實現:
const fetchPosts = postTitle => (dispatch, getState) => {
// 先發送第一個Action,同步的
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
.then(json =>
// 請求到結果後,發送第二個Action,異步
dispatch(receivePosts(postTitle, json)
));
};
};
複製代碼
值得注意的是,Action Creator返回的是一個函數,而正常返回應該是一個對象,而且這個函數的參數是dispatch和getState兩個方法。
這個咱們就須要用到redux-thunk幫咱們處理一下,看下源碼
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
竟是如此簡單的函數,這裏咱們結合applyMiddleware分析一下:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
{},
applyMiddleware(thunk)
);
複製代碼
applyMiddleware函數的參數是thunk函數 ==>
({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}
複製代碼
回顧一下applyMiddleware函數 ==>
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'
export default function applyMiddleware(
...middlewares
) {
return (createStore) => (
reducer,
...args
) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
}
// 中間件增長api
const middlewareAPI = {
getState,
dispatch
}
// chain存放中間件的數組,逐個增長api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose複合函數 a(b(c(0))) => compose(a, b, c)(0)
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
複製代碼
應用了redux-thunk,dispatch函數就會變成
dispatch = (store.dispatch) => (action) => {
// 處理action是函數的狀況
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 處理action是對象的狀況
return store.dispatch(action)
}
複製代碼
總結了這些,若有不對的地方還望你們指正,但願有興趣的朋友一塊兒交流交流。