Redux學習筆記

設計思想

核心概念

  • 全部的狀態存放在Store。組件每次從新渲染,都必須由狀態變化引發。
  • 用戶在 UI 上發出action
  • reducer函數接收action,而後根據當前的state,計算出新的state

動機

  • 隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 須要管理比任什麼時候候都要多的 state (狀態)。經過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測。

生命週期

Redux 應用中數據的生命週期遵循下面 4 個步驟:

調用 store.dispatch(action)。
  • Action 就是一個描述「發生了什麼」的普通對象。好比:
{ type: 'LIKE_ARTICLE', articleId: 42 };
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } };
{ type: 'ADD_TODO', text: 'Read the Redux docs.'};複製代碼
  • 能夠把 action 理解成新聞的摘要。如 「瑪麗喜歡42號文章。」 或者 「任務列表裏添加了'學習 Redux 文檔'」。
    你能夠在任何地方調用 store.dispatch(action),包括組件中、XHR 回調中、甚至定時器中。
Redux store 調用傳入的 reducer 函數。
  • Store 會把兩個參數傳入 reducer: 當前的 state 樹和 action。例如,在這個 todo 應用中,根 reducer 可能接收這樣的數據:
// 當前應用的 state(todos 列表和選中的過濾器)
let previousState = {
    visibleTodoFilter: 'SHOW_ALL',
    todos: [
        {
            text: 'Read the docs.',
            complete: false
        }
    ]
}

// 將要執行的 action(添加一個 todo)
let action = {
    type: 'ADD_TODO',
    text: 'Understand the flow.'
}

// render 返回處理後的應用狀態
let nextState = todoApp(previousState, action);複製代碼
  • 注意 reducer 是純函數。它僅僅用於計算下一個 state。它應該是徹底可預測的:屢次傳入相同的輸入必須產生相同的輸出。它不該作有反作用的操做,如 API 調用或路由跳轉。這些應該在 dispatch(action) 前發生。
根 reducer 應該把多個子 reducer 輸出合併成一個單一的 state 樹。
  • reducer 的結構徹底由你決定。Redux 原生提供combineReducers()輔助函數,來把根 reducer 拆分紅多個函數,用於分別處理 state 樹的一個分支。

-下面演示 combineReducers() 如何使用。假如你有兩個 reducer:一個是 todo 列表,另外一個是當前選擇的過濾器設置:javascript

function todos(state = [], action) {
    // 省略處理邏輯...
    return nextState;
}

function visibleTodoFilter(state = 'SHOW_ALL', action) {
    // 省略處理邏輯...
    return nextState;
}

let todoApp = combineReducers({
    todos,
    visibleTodoFilter
})複製代碼
  • 當你觸發 action 後,combineReducers 返回的 todoApp 會負責調用兩個 reducer:
let nextTodos = todos(state.todos, action);
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action);複製代碼
  • 而後會把兩個結果集合併成一個 state 樹:
return {
    todos: nextTodos,
    visibleTodoFilter: nextVisibleTodoFilter
};複製代碼
  • 雖然 combineReducers() 是一個很方便的輔助工具,你也能夠選擇不用;你能夠自行實現本身的根 reducer
Redux store 保存了根 reducer 返回的完整 state 樹。
  • 這個新的樹就是應用的下一個 state!全部訂閱 store.subscribe(listener) 的監聽器都將被調用;監聽器裏能夠調用 store.getState() 得到當前 state。java

  • 如今,能夠應用新的 state 來更新 UI。若是你使用了 React Redux 這類的綁定庫,這時就應該調用 component.setState(newState) 來更新。react

三大原則

單一數據源

  • 整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。

State 是隻讀的

  • 唯一改變 state 的方法就是觸發 actionaction 是一個用於描述已發生事件的普通對象。

使用純函數來執行修改

  • 爲了描述 action 如何改變 state tree ,你須要編寫 reducers

Redux 核心API

  • Redux的核心是一個store,這個store有Redux提供的createStore(reducers,[initialState])方法生成。從函數簽名看出,想生成store,必須傳入reducers,同時也能夠傳入第二個可選參數初始化狀態initialState

createStore

  • 使用方法 const store = createStore(reducer);redux

  • reducer

    • 在Redux裏,負責響應action並修改數據的角色就是reducerreducer本質上是一個純函數,其函數簽名爲reducer(previousState, action) => newStatereducer在處理action時,需傳入一個previousState參數。reducer的職責就是根據previousStateaction來計算出新的newState
    • 使用方法將reducer即下面的todo做爲參數傳入createStore(todo)中數組

      //如下爲reducer的格式
      const todo = (state = initialState, action) => {
        switch(action.type) {
            case 'XXX':
                return //具體的業務邏輯;
            case 'XXX':
                return //具體的業務邏輯; 
            default:
                return state;
        }
      }複製代碼
  • getState()

    • 使用方法getState()
    • 獲取store中的狀態。
  • dispatch(action)

    • 使用方法store.dispatch(action)
    • 分發一個action,並返回這個action,這是惟一能改變store中數據的方式。store.dispatch接受一個Action對象做爲參數,將它發送出去。
  • subscribe(listener)

    • 使用方法store.subscribe(listenter)
    • 註冊一個監聽者,它在store發生變化時被調用,一旦State發生了變化,就會自動執行這個函數。經過subscribe綁定了一個監聽函數以後,只要dispatch了一個action,全部監聽函數都會自動執行一遍。
  • replaceReducer(nextReducer)

    • 更新當前store裏的reducer,通常只會在開發者模式中調用該方法。
  • createStore的實現

    • 包含getState()dispatch(action)subscribe(listener);本函數近似源碼,可簡單實現功能與幫助理解createStore的原理app

      const createStore = (reducer) => {
        let state; //聲明一個變量承接狀態
        let list = [];//聲明一個數組用於儲存監聽函數
        const getState = () => {
            return state;//直接返回state;
        }
        const dispatch = (action) =>{
            state = reducer(state, action);//更新狀態,且循環list數組,並執行裏面的事件
            list.forEach((fn) => {
                fn();
            })
        }
        const subscribe = (fn) => {
            list.push(fn);//將函數傳入list中
            return () => {
                list = list.filter(cd => cd != fn)
            }
        }
        return {
            getState,
            subscribe,
            dispatch
        }
      }複製代碼
  • combineReducers

    • 隨着應用變得複雜,須要對 reducer 函數 進行拆分,拆分後的每一塊獨立負責管理 state 的一部分。combineReducers 輔助函數的做用是,把一個由多個不一樣 reducer 函數做爲 valueobject,合併成一個最終的 reducer 函數,而後就能夠對這個 reducer 調用 createStore
  • 使用方法

    • 將多個不一樣的reducer做爲對象的屬性傳入combineReducers({})函數中,
const rootReducer = combineReducers({
    reducer1,
    reducer2,
    ...
})複製代碼
  • 返回值

    • (Function):一個調用 reducers 對象裏全部 reducer 的 reducer,而且構造一個與 reducers 對象結構相同的 state 對象。ide

      let store = createStore(rootReducer)
        //store = {
            reducer1: ... ,
            reducer2: ... ,
            ...
        }
        let {reducer1, reducer2} = store; //取出複製代碼
  • 模擬實現combineReducers

//研究邏輯看這個
const combineReducers = (reducers) => {
    return (state = {}, action) => {
        let newState = {};
        Object.keys(reducers).forEach((key) =>{
            newState[key] = reducers[key](state[key], action);
        })
        return newState;
    }
}

//簡寫裝逼看這個
const combineReducers = (reducers) => (state = {}, action) => Object.keys(reducers).reduce((newState, key) => {
    newState[key] = reducers[key](state[key], action);
    return newState;
},{})複製代碼

react的跨級組件通訊(蟲洞)幫助理解(react-redux)

  • 此方法爲React中的方法,隨着應用變的愈來愈複雜,組件嵌套愈來愈深,有時要從最外層將一個數據一直傳遞到最裏層。
  • 理論上,經過prop一層層傳遞下去固然是沒問題的。不過這也太麻煩啦,要是能在最外層和最裏層之間開一個穿越空間的蟲洞就行了。
  • React的開發者也意識到這個問題,爲咱們開發出了這個空間穿越通道 —— Context
  • 注意:context一直都在React源碼中,但在React0.14版本才被記錄官方文檔。官方並不太推薦大量使用,雖然它能夠減小逐級傳遞,但當組件複雜時,咱們並不知道context是從哪傳來的。它就相似於全局變量。函數

  • context使用方法

    • 在外層定義一個getChildContext方法,在父層制定childContextTypes工具

      class Provider extends Component{
        getChildContext() {
            return {store: ...};
        }
        render(){
            return(
                this.props.children
            )
        }
      }
      
      Provider.childContextTypes = {
        store : React.PropTypes.object
      };複製代碼
    • 在內層設置組件的contextTypes後,便可在組件裏經過this.context.來訪問。學習

      class child extends Component{
        render(){
            const store = this.context.store;
            return(
                <div>1</div>
            )
        }
      }
      child.contextTypes = {
        store: React.PropTypes.object
      }複製代碼

解讀react-redux

  • 前面說到Redux的核心只有一個createStore()方法,這樣還不足以讓Redux在咱們的react應用中發揮做用,還須要react-redux庫 ———— Redux官方提供的React綁定。
  • react-redux提供了一個組件和一個API幫助Redux和React進行綁定,一個是React組件<Provider />, 一個是connect()。關於它們,咱們須要知道的是,<Provider />接受一個store做爲props,它是整個Redux應用的頂層組件,而connect()提供了在整個React應用的任意組件中獲取store中數據的功能。

Provider

  • 其實就是建立一個外層包裹住整個Redux應用。

  • <Provider />主要源碼

export default class Provider extends Component {
    getChildContext() {
        return { store: this.store }
    }

    constructor(props, context) {
        super(props, context)
        this.store = props.store
    }

    render() {
        return Children.only(this.props.children)
    }
}複製代碼
  • 用法
ReactDom.render(
    <Provider store = {store}> <App /> </Provider>,
    document.getElementById("root")
)複製代碼

connect

  • 使用方法connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(TodoApp)

  • 上面代碼看似那麼長,但其實理解起來不太難,前四個參數是選填屬性,根據需求填入便可。connect(...)調用後會返回一個函數這個函數可傳一個參數,即你須要綁定的組件。

  • 模擬實現connect函數,只針對前兩個關鍵參數。
const connect = (mapStateToProps, mapDispatchToProps) => {
    return (WrapperComponent) => {
    class Connect extends Component {
        componentDidMount() {
            const store = this.context.store;
            this.unsubscribe = store.subscribe(() => {
                this.forceUpdate();
            })
        }
        componentWillUnmount() {
            this.unsubscribe();
        }
        render (){
            const store = this.context.store;
            const stateProps = mapStateToProps(store.getState());
            const dispatchProps = mapDispatchToProps(store.dispatch);
            const props = Object.assign({}, stateProps, dispatchProps);

            // return <WrapperComponent {...props} />;
            return React.createElement(WrapperComponent, props);
        }
    }
    Connect.contextTypes = {
        store: React.PropTypes.object
    };
        return Connect;
    }
}複製代碼

mapStateToProps

  • 官方解釋: 若是定義該參數,組件將會監聽 Redux store 的變化。任什麼時候候,只要 Redux store 發生改變,mapStateToProps 函數就會被調用。該回調函數必須返回一個純對象,這個對象會與組件的 props 合併。若是你省略了這個參數,你的組件將不會監聽 Redux store。若是指定了該回調函數中的第二個參數 ownProps,則該參數的值爲傳遞到組件的 props,並且只要組件接收到新的 propsmapStateToProps 也會被調用。

  • 使用方法(其實裏面第一個參數就是最先在 <Provider store = {store}>傳入的store,因而能夠在子組件上訪問store裏的屬性)

const mapStateToProps = (state, [ownProps]) => {
    return {
        todos : state.todos
    }
}複製代碼

mapDispatchToProps

  • 官方解釋: 若是傳遞的是一個對象,那麼每一個定義在該對象的函數都將被看成 Redux action creator,並且這個對象會與 Redux store 綁定在一塊兒,其中所定義的方法名將做爲屬性名,合併到組件的 props 中。若是傳遞的是一個函數,該函數將接收一個 dispatch 函數,而後由你來決定如何返回一個對象,這個對象經過 dispatch 函數與 action creator 以某種方式綁定在一塊兒(提示:你也許會用到 Redux 的輔助函數 bindActionCreators())。若是你省略這個 mapDispatchToProps 參數,默認狀況下,dispatch 會注入到你的組件 props 中。若是指定了該回調函數中第二個參數 ownProps,該參數的值爲傳遞到組件的 props,並且只要組件接收到新 propsmapDispatchToProps 也會被調用。
  • 使用方法(用於傳遞方法)。其實總而言之,mapStateToProps是用來傳遞屬性狀態的,而mapDispatchToProps是用來傳遞改變的方法的。
const mapDispatchToProps = (dispatch, [ownProps]) => {
    return{
        ... : () => {
            dispatch(...)
        }
    }
}複製代碼

mergeProps

  • mergeProps(stateProps, dispatchProps, ownProps)能夠接受stateProps, dispatchProps, ownProps三個參數。
  • stateProps就是傳給connect的第一個參數mapStateToProps最終返回的props
  • dispatchProps就是傳給connect的第二個參數mapDispatchToProps最終返回的props
  • ownProps則爲組件本身的props

options

  • 若是指定這個參數,能夠定製 connector 的行爲。

    • [pure = true] (Boolean): 若是爲 trueconnector 將執行 shouldComponentUpdate 而且淺對比 mergeProps 的結果,避免沒必要要的更新,前提是當前組件是一個「純」組件,它不依賴於任何的輸入或 state 而只依賴於 props 和 Redux storestate。默認值爲 true

    • [withRef = false] (Boolean): 若是爲 trueconnector 會保存一個對被包裝組件實例的引用,該引用經過 getWrappedInstance() 方法得到。默認值爲 false

相關文章
相關標籤/搜索