前面有一個Redux,咱們去撩(聊)一下它。

什麼是Redux?

管理整個前端項目(單頁應用)全部的狀態數據,統一把整個應用的狀態存到一個地方(store),保存成一個狀態樹,修改數據須要派發(dispatch)一個動做(action)通知store修改。組件經過訂閱(subscribe)修改事件,獲取最新數據來修改自身狀態。javascript

三大原則

  1. 整個應用的state存儲在store中,有且只存在一個store。
  2. store裏面的state是隻讀的,惟一改變state的方法就是派發(dispatch)一個動做(action)。
  3. 純函數(reducer)修改state,每次返回一個新的state,不能直接修改原對象。

爲何要使用Redux(應用場景)

  1. 單頁應用複雜,管理不斷變化的state很是困難。
  2. 非父子關係組件通訊。
  3. 全部頁面的公用狀態。

從源碼出發,整個redux源碼,總共導出5個函數,和一個__DO_NOT_USE__ActionTypes(默認的action,全部action類型不能重複,下面會細撩這個點),那麼下面就來細細的撩一下5個函數。前端

1.createStore(reducer, preloadedState, enhancer)

想要使用redux,首先就是建立一個全局的store(固然是惟一的一個),就得調用這個函數(createStore)。該函數接收三個參數。store裏面保存了全部的數據,能夠看作一個容器。java

傳入reducerinitState建立store。react

store返回給鑰匙,修改器,電話git

有了鑰匙就能隨時取數據,若是須要修改數據就得經過修改器,如須要須要知道數據何時修改了,就打一個電話給store,告訴它,數據修改好了給我說。github

先來看看它返回的函數:

getState() => currentState

返回當前的store狀態樹,包含全部state。這裏在讀源碼的時候發現一個疑問。 redux

這裏返回的是本來的對象,那麼外部拿到這個state,不是能夠直接修改嗎?這樣違背了 只讀。爲何不返回一個新引用對象,防止此操做。

帶着這個疑問,給Redux提了一個PR. 獲得回覆:app

Yes - getState() just returns whatever the current state value is. It's up to you to avoid accidentally mutating it.ide

(譯文)是的,getState()只返回當前狀態值。你要避免不當心把它弄亂。函數

也就是說在這裏須要注意一下,getState()返回的值不能直接修改,不然你將陷入深淵

subscribe(listener) => unsubscribe()

傳入一個函數用來監聽store發生變化,一旦store發生了變化,將循環執行全部的監聽函數。調用該函數還返回了一個取消監聽的函數。調用返回的函數,則取消該listener監聽。

dispatch(action) => action

dispatch接收一個action。在函數內部會把全部的reducer執行一遍並把當前的state和action傳入reducer,而後再執行全部的監聽函數。從源碼中截了一段出來:

const dispatch = (action) => {
	currentState = currentReducer(currentState, action);
	
	const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    
    return action
}
複製代碼
action

一個對象,必須包含一個type,表示所要發生的動做,其餘屬性自由設置,社區中有一個規範,把其餘全部數據放入payload中,通常開發中會寫一個函數來生成action。

function login(){
		retrun {
			type: 'LOGIN',
			payload: {
				username: 'xxx',
				passworld: 'xxxx'
			}
		}
	}
複製代碼

replaceReducer(nextReducer)

傳入新的reducer,替換以前的reducer,而且派發一個ActionType爲REPLACE的action。

再來看看它接收的三個參數

reducer

一個純函數,接收當前的state和action作爲參數,返回新的state。

Store 收到 Action 之後,會調用reducer,必須給出一個新的 State,這樣 Store裏的數據纔會發生變化。

編寫一個簡單的reducer

const reducer = function (state, action) {
	switch(action.type){
		case 'TYPE':
  			return {...state, newState}
  		default:
  			return state;
	}
};	
複製代碼

preloadedState

源碼裏面的參數名叫這個,其實我更願意叫它initState,初始化的狀態,爲何源碼不叫initState呢?由於不許確,在源碼裏會默認發送一個type爲init的dispatch(),這個時候走到reducer裏面(看看上面的reducer代碼),state若是在這個時候設置一個默認值,好比:

const reducer = function (state = {list: []}, action) {
	switch(action.type){
		case 'TYPE':
  			return {...state, newState}
  		default:
  			return state;
	}
};	
複製代碼

這個時候就默認返回了{list: []},就給出了一個真正的initState。

enhancer

用來配合快速建立中間件的。


上面提到的__DO_NOT_USE__ActionTypes,就是2個actionType:

  • @@redux/INIT: 用來內部發送一個默認的dispatch
  • @@redux/REPLACE: 用來替換reducer

2.bindActionCreators(actionCreators, dispatch) => boundActionCreators

遍歷全部生成action函數並執行,返回被dispatch包裹的函數,可供直接調用派發一個請求。

actionCreators

該參數爲一個對象,包含生成action的函數,例如:

const actionCreators = {
	login: () => {
		return {
			type: 'LOGIN',
			payload: {
				username: 'xxx',
				passworld: 'xxxx'
			}
		}
	},
	logout: () => {
		retrun {
			type: 'LOGOUT'
		}
	}	
}
複製代碼

若是傳入一個函數,則執行函數獲得action,返回一個dispatch(action)。

dispatch

這裏就是createStore所返回的dispatch


該函數返回對象或函數(根據傳入的actionCreators來決定),能夠直接調用xx.login()去派發一個登錄。

3.combineReducers(reducers)

在項目開發中,須要分模塊寫reducer,利用該函數合併多個reducer模塊。傳入一個reducer集合。

a.js
export (state = {list: []}, action) => {
	switch (action.type) {
	    case "LIST_CHANGE":
	        return { ...state, ...action.payload };
	    default:
	        return state;
	}
}

b.js
export (state = {list: []}, action) => {
	switch (action.type) {
	    case "LIST":
	        return { ...state, ...action.payload };
	    default:
	        return state;
	}
}

combineReducers.js
import a from './a';
import b from './b';

combineReducers({
	a: a,
	b: b
})
複製代碼

ab都有list這個state,可是他們並不相關,要把他們分開使用,就得用combineReducers去合併。

下面簡單實現了該函數:

function combineReducers(reducers) {
    return (state = {}, action) => {
        return Object.keys(reducers).reduce((currentState, key) => {
            currentState[key] = reducers[key](state[key], action);
            return currentState;
        }, {})
    };
}
複製代碼

4.compose

能夠說是js中函數式中很重要的方法,把一堆函數串聯起來執行,從右至左執行(也就是倒序),函數的參數是上一個函數的結果。看一個使用例子:

const fn1 = (val) => val + 'fn1';

const fn2 = (val) => val + 'fn2';

const fn3 = (val) => val + 'fn3';

const dispatch = compose(fn1, fn2, fn3);

console.log(dispatch('test'));
複製代碼

最終輸出結果testfn3fn2fn1

test傳給fn3當參數,fn3的返回值給了fn2....

compose.js

function compose(...fns){
    if(fns.length==1) return fns[0];
    return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
複製代碼

5.applyMiddleware

該函數是用來添加中間件,在修改數據的時候作一些其餘操做,redux經過改造dispatch來實現中間件.

使用中間件

const middleware = applyMiddleware(logger);
複製代碼

須要傳入中間件函數,能夠是多個函數,依次傳入,在applyMiddleware裏面用到了compose,因此咱們傳入的函數的從右至左依次執行的,這裏須要注意一下。

const createStoreWithMiddleware = middleware(createStore);
複製代碼

由於中間件是經過改造dispatch來實現,因此須要把建立store的方法傳進去。

const store = createStoreWithMiddleware(reducer, preloadedState)
複製代碼

這裏再傳入createStore須要接收的參數,返回store對象。

實現一個logger中間件

const logger = function({dispatch, getState}){
   return function(next){
      return function(action){
	        console.log('oldState',getState());
	        next(action); // 真實派發動做
	        console.log('newState',getState());
	    }
    }
}
複製代碼

首先middleware會把未改造的dispatchgetState傳入進來,這裏的next至關於dispatch,去派發一個真正的修改數據動做。

源碼貼上:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼

middlewareAPI是存儲未改造的方法,compose上面講過,第一個參數傳入的就是dispatch,返回的一個改造後dispatch就是經過compose事後的一個函數,會依次執行。

最後

一些學習心得,若有不對歡迎指正
個人github 謝謝你閱讀個人文章

相關文章
相關標籤/搜索