什麼是Redux?
管理整個前端專案(單頁應用)所有的狀態資料,統一把整個應用的狀態存到一個地方(store),儲存成一個狀態樹,修改資料需要派發(dispatch)一個動作(action)通知store修改。元件通過訂閱(subscribe)修改事件,獲取最新資料來修改自身狀態。
三大原則
- 整個應用的state儲存在store中,有且只存在一個store。
- store裡面的state是隻讀的,唯一改變state的方法就是派發(dispatch)一個動作(action)。
- 純函式(reducer)修改state,每次返回一個新的state,不能直接修改原物件。
為什麼要使用Redux(應用場景)
- 單頁應用複雜,管理不斷變化的state非常困難。
- 非父子關係元件通訊。
- 所有頁面的公用狀態。
從原始碼出發,整個redux原始碼,總共匯出5個函式,和一個__DO_NOT_USE__ActionTypes
(預設的action,所有action型別不能重複,下面會細撩這個點),那麼下面就來細細的撩一下5個函式。
1.createStore(reducer, preloadedState, enhancer)
想要使用redux,首先就是建立一個全域性的store(當然是唯一的一個),就得呼叫這個函式(createStore)。該函式接收三個引數。store裡面儲存了所有的資料,可以看做一個容器。
傳入reducer
和initState
建立store。
store返回給鑰匙
,修改器
,電話
。
有了鑰匙就能隨時取資料,如果需要修改資料就得通過修改器,如需要需要知道資料什麼時候修改了,就打一個電話給store,告訴它,資料修改好了給我說。
先來看看它返回的函式:
getState() => currentState
返回當前的store狀態樹,包含所有state。這裡在讀原始碼的時候發現一個疑問。
這裡返回的是原本的物件,那麼外部拿到這個state,不是可以直接修改嗎?這樣違背了只讀。為什麼不返回一個新引用物件,防止此操作。帶著這個疑問,給Redux提了一個PR. 得到回覆:
Yes - getState() just returns whatever the current state value is. It's up to you to avoid accidentally mutating it.
(譯文)是的,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
})
複製程式碼
a
和b
都有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會把未改造的dispatch
和getState
傳入進來,這裡的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
謝謝你閱讀我的文章