1. 程式人生 > >Redux初學理解

Redux初學理解

獲取 export creates server 有變 from watermark tex 對象

Redux是什麽?

一個狀態(State)管理器,集中管理應用中所有組件的狀態。
所有組件的狀態保存在一個對象裏。


Redux主要用途?

1)簡化組件依賴關系,使可以共享或改變所有組件的狀態。
2)狀態的改變,會引起視圖的改變,所以如果想改變視圖(View),不管涉及誰的改變,統一找Redux就行了,因為狀態都在他那裏集中管理。
3)Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麽樣,反之亦然。


如何使用?

1)創建唯一的一個store:

    import { createStore } from ‘redux‘;
    const store = createStore(fn);

2)獲取state

    const state = store.getState();

3)改變state,需要發送Action通知。
Action 就是 View 發出的通知,表示 State 應該要發生變化了。
發送通知的方法是store.dispatch(action)。如下:

    import { createStore } from ‘redux‘;
    const store = createStore(fn);
    store.dispatch({
        type: ‘ADD_TODO‘,
        payload: ‘Learn Redux‘
    });

Actiond的結構:

    const action = {
        type: ‘ADD_TODO‘,
        payload: ‘Learn Redux‘
    };

type是必須的,用於識別一個action,其他是可選的。
可以寫一些輔助函數來創建action,避免dispatch看著太復雜。

4)Reducer
Store 收到 Action 以後,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函數,它接受 Action 和當前 State 作為參數,返回一個新的 State。


這個函數,是在store創建是傳入的,就是上面創建描述的:const store = createStore(fn),也就是:

        const store = createStore(reducer)

以後每當store.dispatch發送過來一個新的 Action,就會自動調用 Reducer,得到新的 State。

Reducer 函數最重要的特征是,它是一個純函數。也就是說,只要是同樣的(類型)輸入,必定得到同樣的(類型)輸出。
純函數是函數式編程的概念,必須遵守以下一些約束。

        不得改寫參數
        不能調用系統 I/O 的API
        不能調用Date.now()或者Math.random()等不純的方法,因為每次會得到不一樣的結果

由於 Reducer 是純函數,就可以保證同樣的State,必定得到同樣的 View。但也正因為這一點,Reducer 函數裏面不能改變 State,必須返回一個全新的對象。

5)store.subscribe()

Store 允許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。

    import { createStore } from ‘redux‘;
    const store = createStore(reducer);
    store.subscribe(listener);

顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染。

store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。

        let unsubscribe = store.subscribe(() =>
            console.log(store.getState())
        );

        unsubscribe();

Store 的實現

以上介紹了 Redux 涉及的基本概念,可以發現 Store 提供了三個方法。

    store.getState()
    store.dispatch()
    store.subscribe()

    import { createStore } from ‘redux‘;
    let { subscribe, dispatch, getState } = createStore(reducer);

createStore方法還可以接受第二個參數,表示 State 的最初狀態。這通常是服務器給出的。

    let store = createStore(todoApp, window.STATE_FROM_SERVER)

上面代碼中,window.STATE_FROM_SERVER就是整個應用的狀態初始值。註意,如果提供了這個參數,它會覆蓋 Reducer 函數的默認初始值。

下面是createStore方法的一個簡單實現,可以了解一下 Store 是怎麽生成的。(以下代碼僅僅用於理解原理,屬於進階教程)

    const createStore = (reducer) => {
        let state;
        let listeners = [];

        const getState = () => state;

        const dispatch = (action) => {
            state = reducer(state, action);
            listeners.forEach(listener => listener());
        };

        const subscribe = (listener) => {
            listeners.push(listener);
            return () => {
                listeners = listeners.filter(l => l !== listener);
            }
        };

        dispatch({});

        return { getState, dispatch, subscribe };
    };          

Reducer 的拆分

Reducer 函數負責生成 State。由於整個應用只有一個 State 對象,包含所有數據,對於大型應用來說,這個 State 必然十分龐大,導致 Reducer 函數也十分龐大。

請看下面的例子。

            const chatReducer = (state = defaultState, action = {}) => {
                const { type, payload } = action;  //解包
                switch (type) {
                    case ADD_CHAT:
                        return Object.assign({}, state, {
                            chatLog: state.chatLog.concat(payload)
                        });
                    case CHANGE_STATUS:
                        return Object.assign({}, state, {
                            statusMessage: payload
                        });
                    case CHANGE_USERNAME:
                        return Object.assign({}, state, {
                            userName: payload
                        });
                    default: return state;
                }
            };

上面代碼中,三種 Action 分別改變 State 的三個屬性。

ADD_CHAT:chatLog屬性
CHANGE_STATUS:statusMessage屬性
CHANGE_USERNAME:userName屬性
這三個屬性之間沒有聯系,這提示我們可以把 Reducer 函數拆分。不同的函數負責處理不同屬性,最終把它們合並成一個大的 Reducer 即可。

    const chatReducer = (state = defaultState, action = {}) => {
        return {
            chatLog: chatLog(state.chatLog, action),
            statusMessage: statusMessage(state.statusMessage, action),
            userName: userName(state.userName, action)
        }
    };

上面代碼中,Reducer 函數被拆成了三個小函數,每一個負責生成對應的屬性。

這樣一拆,Reducer 就易讀易寫多了。而且,這種拆分與 React 應用的結構相吻合:一個 React 根組件由很多子組件構成。這就是說,子組件與子 Reducer 完全可以對應。

Redux 提供了一個combineReducers方法,用於 Reducer 的拆分。你只要定義各個子 Reducer 函數,然後用這個方法,將它們合成一個大的 Reducer。

        import { combineReducers } from ‘redux‘;

        const chatReducer = combineReducers({
            chatLog,
            statusMessage,
            userName
        })

        export default todoApp;

上面的代碼通過combineReducers方法將三個子 Reducer 合並成一個大的函數。

這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。如果不同名,就要采用下面的寫法。

        const reducer = combineReducers({
            a: doSomethingWithA,
            b: processB,
            c: c
        })

        // 等同於
        function reducer(state = {}, action) {
            return {
                a: doSomethingWithA(state.a, action),
                b: processB(state.b, action),
                c: c(state.c, action)
            }
        }

總之,combineReducers()做的就是產生一個整體的 Reducer 函數。該函數根據 State 的 key 去執行相應的子 Reducer,並將返回結果合並成一個大的 State 對象。

下面是combineReducer的簡單實現(實現,告訴你原理,所以然)。

        const combineReducers = reducers => {
            return (state = {}, action) => {
                return Object.keys(reducers).reduce(
                    (nextState, key) => {
                        nextState[key] = reducers[key](state[key], action);
                        return nextState;
                    },
                    {} 
                );
            };
        };

你可以把所有子 Reducer 放在一個文件裏面,然後統一引入。

        import { combineReducers } from ‘redux‘
        import * as reducers from ‘./reducers‘

        const reducer = combineReducers(reducers)

工作流程
本節對 Redux 的工作流程,做一個梳理。

技術分享圖片

首先,用戶發出 Action。

    store.dispatch(action);

然後,Store 自動調用 Reducer,並且傳入兩個參數:當前 State 和收到的 Action。 Reducer 會返回新的 State 。

    let nextState = todoApp(previousState, action);

State 一旦有變化,Store 就會調用監聽函數。

// 設置監聽函數
store.subscribe(listener);
listener可以通過store.getState()得到當前狀態。如果使用的是 React,這時可以觸發重新渲染 View。

    function listerner() {
        let newState = store.getState();
        component.setState(newState);   
    }

Redux初學理解