前面有一個(gè)Redux,我們?nèi)チ?聊)一下它伤塌。

什么是Redux?

管理整個(gè)前端項(xiàng)目(單頁應(yīng)用)所有的狀態(tài)數(shù)據(jù)灯萍,統(tǒng)一把整個(gè)應(yīng)用的狀態(tài)存到一個(gè)地方(store),保存成一個(gè)狀態(tài)樹寸谜,修改數(shù)據(jù)需要派發(fā)(dispatch)一個(gè)動作(action)通知store修改竟稳。組件通過訂閱(subscribe)修改事件属桦,獲取最新數(shù)據(jù)來修改自身狀態(tài)熊痴。

三大原則

  1. 整個(gè)應(yīng)用的state存儲在store中,有且只存在一個(gè)store聂宾。
  2. store里面的state是只讀的果善,唯一改變state的方法就是派發(fā)(dispatch)一個(gè)動作(action)。
  3. 純函數(shù)(reducer)修改state系谐,每次返回一個(gè)新的state巾陕,不能直接修改原對象。

為什么要使用Redux(應(yīng)用場景)

  1. 單頁應(yīng)用復(fù)雜纪他,管理不斷變化的state非常困難鄙煤。
  2. 非父子關(guān)系組件通信。
  3. 所有頁面的公用狀態(tài)茶袒。

從源碼出發(fā)梯刚,整個(gè)redux源碼,總共導(dǎo)出5個(gè)函數(shù)薪寓,和一個(gè)__DO_NOT_USE__ActionTypes(默認(rèn)的action,所有action類型不能重復(fù)亡资,下面會細(xì)撩這個(gè)點(diǎn)),那么下面就來細(xì)細(xì)的撩一下5個(gè)函數(shù)向叉。

1.createStore(reducer, preloadedState, enhancer)

想要使用redux锥腻,首先就是創(chuàng)建一個(gè)全局的store(當(dāng)然是唯一的一個(gè)),就得調(diào)用這個(gè)函數(shù)(createStore)母谎。該函數(shù)接收三個(gè)參數(shù)瘦黑。store里面保存了所有的數(shù)據(jù),可以看做一個(gè)容器奇唤。

image

傳入reducerinitState創(chuàng)建store幸斥。

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

有了鑰匙就能隨時(shí)取數(shù)據(jù)冻记,如果需要修改數(shù)據(jù)就得通過修改器睡毒,如需要需要知道數(shù)據(jù)什么時(shí)候修改了,就打一個(gè)電話給store冗栗,告訴它演顾,數(shù)據(jù)修改好了給我說供搀。

先來看看它返回的函數(shù):

getState() => currentState

返回當(dāng)前的store狀態(tài)樹,包含所有state钠至。這里在讀源碼的時(shí)候發(fā)現(xiàn)一個(gè)疑問葛虐。

image

這里返回的是原本的對象,那么外部拿到這個(gè)state棉钧,不是可以直接修改嗎屿脐?這樣違背了只讀。為什么不返回一個(gè)新引用對象宪卿,防止此操作的诵。

帶著這個(gè)疑問,給Redux提了一個(gè)PR. 得到回復(fù):

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

(譯文)是的佑钾,getState()只返回當(dāng)前狀態(tài)值西疤。你要避免不小心把它弄亂。

也就是說在這里需要注意一下休溶,getState()返回的值不能直接修改代赁,否則你將陷入深淵

subscribe(listener) => unsubscribe()

傳入一個(gè)函數(shù)用來監(jiān)聽store發(fā)生變化,一旦store發(fā)生了變化兽掰,將循環(huán)執(zhí)行所有的監(jiān)聽函數(shù)芭碍。調(diào)用該函數(shù)還返回了一個(gè)取消監(jiān)聽的函數(shù)。調(diào)用返回的函數(shù)孽尽,則取消該listener監(jiān)聽窖壕。

dispatch(action) => action

dispatch接收一個(gè)action。在函數(shù)內(nèi)部會把所有的reducer執(zhí)行一遍并把當(dāng)前的state和action傳入reducer泻云,然后再執(zhí)行所有的監(jiān)聽函數(shù)艇拍。從源碼中截了一段出來:

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

一個(gè)對象,必須包含一個(gè)type宠纯,表示所要發(fā)生的動作卸夕,其他屬性自由設(shè)置,社區(qū)中有一個(gè)規(guī)范婆瓜,把其他所有數(shù)據(jù)放入payload中快集,一般開發(fā)中會寫一個(gè)函數(shù)來生成action。

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

replaceReducer(nextReducer)

傳入新的reducer廉白,替換之前的reducer个初,并且派發(fā)一個(gè)ActionType為REPLACE的action。

再來看看它接收的三個(gè)參數(shù)

reducer

一個(gè)純函數(shù)猴蹂,接收當(dāng)前的state和action做為參數(shù)院溺,返回新的state。

Store 收到 Action 以后磅轻,會調(diào)用reducer珍逸,必須給出一個(gè)新的 State逐虚,這樣 Store里的數(shù)據(jù)才會發(fā)生變化。

編寫一個(gè)簡單的reducer

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

preloadedState

源碼里面的參數(shù)名叫這個(gè)谆膳,其實(shí)我更愿意叫它initState叭爱,初始化的狀態(tài),為什么源碼不叫initState呢漱病?因?yàn)椴粶?zhǔn)確买雾,在源碼里會默認(rèn)發(fā)送一個(gè)type為init的dispatch(),這個(gè)時(shí)候走到reducer里面(看看上面的reducer代碼)杨帽,state如果在這個(gè)時(shí)候設(shè)置一個(gè)默認(rèn)值漓穿,比如:

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

這個(gè)時(shí)候就默認(rèn)返回了{(lán)list: []},就給出了一個(gè)真正的initState睦尽。

enhancer

用來配合快速創(chuàng)建中間件的器净。


上面提到的__DO_NOT_USE__ActionTypes型雳,就是2個(gè)actionType:

  • @@redux/INIT: 用來內(nèi)部發(fā)送一個(gè)默認(rèn)的dispatch
  • @@redux/REPLACE: 用來替換reducer

2.bindActionCreators(actionCreators, dispatch) => boundActionCreators

遍歷所有生成action函數(shù)并執(zhí)行当凡,返回被dispatch包裹的函數(shù),可供直接調(diào)用派發(fā)一個(gè)請求纠俭。

actionCreators

該參數(shù)為一個(gè)對象沿量,包含生成action的函數(shù),例如:

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

如果傳入一個(gè)函數(shù)冤荆,則執(zhí)行函數(shù)得到action朴则,返回一個(gè)dispatch(action)。

dispatch

這里就是createStore所返回的dispatch



該函數(shù)返回對象或函數(shù)(根據(jù)傳入的actionCreators來決定)钓简,可以直接調(diào)用xx.login()去派發(fā)一個(gè)登陸乌妒。

3.combineReducers(reducers)

在項(xiàng)目開發(fā)中,需要分模塊寫reducer外邓,利用該函數(shù)合并多個(gè)reducer模塊撤蚊。傳入一個(gè)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都有l(wèi)ist這個(gè)state损话,但是他們并不相關(guān)侦啸,要把他們分開使用,就得用combineReducers去合并丧枪。

下面簡單實(shí)現(xiàn)了該函數(shù):

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中函數(shù)式中很重要的方法光涂,把一堆函數(shù)串聯(lián)起來執(zhí)行,從右至左執(zhí)行(也就是倒序)拧烦,函數(shù)的參數(shù)是上一個(gè)函數(shù)的結(jié)果忘闻。看一個(gè)使用例子:

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'));

最終輸出結(jié)果testfn3fn2fn1

test傳給fn3當(dāng)參數(shù)恋博,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

該函數(shù)是用來添加中間件齐佳,在修改數(shù)據(jù)的時(shí)候做一些其他操作葵蒂,redux通過改造dispatch來實(shí)現(xiàn)中間件.

使用中間件

const middleware = applyMiddleware(logger);

需要傳入中間件函數(shù),可以是多個(gè)函數(shù)重虑,依次傳入践付,在applyMiddleware里面用到了compose,所以我們傳入的函數(shù)的從右至左依次執(zhí)行的缺厉,這里需要注意一下永高。

const createStoreWithMiddleware = middleware(createStore);

因?yàn)橹虚g件是通過改造dispatch來實(shí)現(xiàn),所以需要把創(chuàng)建store的方法傳進(jìn)去提针。

const store = createStoreWithMiddleware(reducer, preloadedState)

這里再傳入createStore需要接收的參數(shù)命爬,返回store對象。

實(shí)現(xiàn)一個(gè)logger中間件

const logger = function({dispatch, getState}){
   return function(next){
      return function(action){
            console.log('oldState',getState());
            next(action); // 真實(shí)派發(fā)動作
            console.log('newState',getState());
        }
    }
}

首先middleware會把未改造的dispatchgetState傳入進(jìn)來辐脖,這里的next相當(dāng)于dispatch饲宛,去派發(fā)一個(gè)真正的修改數(shù)據(jù)動作。

源碼貼上:

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上面講過艇抠,第一個(gè)參數(shù)傳入的就是dispatch,返回的一個(gè)改造后dispatch就是通過compose過后的一個(gè)函數(shù)久锥,會依次執(zhí)行家淤。

最后

一些學(xué)習(xí)心得,如有不對歡迎指正

我的github

謝謝你閱讀我的文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瑟由,一起剝皮案震驚了整個(gè)濱河市絮重,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歹苦,老刑警劉巖青伤,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殴瘦,居然都是意外死亡狠角,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門痴施,熙熙樓的掌柜王于貴愁眉苦臉地迎上來擎厢,“玉大人,你說我怎么就攤上這事辣吃《猓” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵神得,是天一觀的道長厘惦。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么宵蕉? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任酝静,我火速辦了婚禮,結(jié)果婚禮上羡玛,老公的妹妹穿的比我還像新娘别智。我一直安慰自己,他們只是感情好稼稿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布薄榛。 她就那樣靜靜地躺著,像睡著了一般让歼。 火紅的嫁衣襯著肌膚如雪敞恋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天谋右,我揣著相機(jī)與錄音硬猫,去河邊找鬼。 笑死改执,一個(gè)胖子當(dāng)著我的面吹牛啸蜜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播天梧,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盔性,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呢岗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤蛹尝,失蹤者是張志新(化名)和其女友劉穎后豫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體突那,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挫酿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愕难。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片早龟。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖猫缭,靈堂內(nèi)的尸體忽然破棺而出葱弟,到底是詐尸還是另有隱情,我是刑警寧澤猜丹,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布芝加,位于F島的核電站,受9級特大地震影響射窒,放射性物質(zhì)發(fā)生泄漏藏杖。R本人自食惡果不足惜将塑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝌麸。 院中可真熱鬧点寥,春花似錦、人聲如沸来吩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽误褪。三九已至责鳍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兽间,已是汗流浹背历葛。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘀略,地道東北人恤溶。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像帜羊,于是被迫代替她去往敵國和親咒程。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容