【轉】Redux教程

本篇文章轉自阮一峯老師寫的詳細教程。javascript

第一部分

1、基本概念和 API

1.1 Store

Store 就是保存數據的地方,你能夠把它當作一個容器。整個應用只能有一個 Store。html

Redux 提供createStore這個函數,用來生成 Store。java

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

上面代碼中,createStore函數接受另外一個函數做爲參數,返回新生成的 Store 對象。react

1.2 State

Store對象包含全部數據。若是想獲得某個時點的數據,就要對 Store 生成快照。這種時點的數據集合,就叫作 State。git

當前時刻的 State,能夠經過store.getState()拿到。github

import { createStore } from 'redux'; const store = createStore(fn); const state = store.getState(); 

Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麼樣,反之亦然。編程

1.3 Action

State 的變化,會致使 View 的變化。可是,用戶接觸不到 State,只能接觸到 View。因此,State 的變化必須是 View 致使的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。json

Action 是一個對象。其中的type屬性是必須的,表示 Action 的名稱。其餘屬性能夠自由設置,社區有一個規範能夠參考。redux

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

上面代碼中,Action 的名稱是ADD_TODO,它攜帶的信息是字符串Learn Redux數組

能夠這樣理解,Action 描述當前發生的事情。改變 State 的惟一辦法,就是使用 Action。它會運送數據到 Store。

1.4 Action Creator

View 要發送多少種消息,就會有多少種 Action。若是都手寫,會很麻煩。能夠定義一個函數來生成 Action,這個函數就叫 Action Creator。

const ADD_TODO = '添加 TODO'; function addTodo(text) { return { type: ADD_TODO, text } } const action = addTodo('Learn Redux'); 

上面代碼中,addTodo函數就是一個 Action Creator。

1.5 store.dispatch()

store.dispatch()是 View 發出 Action 的惟一方法。

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

上面代碼中,store.dispatch接受一個 Action 對象做爲參數,將它發送出去。

結合 Action Creator,這段代碼能夠改寫以下。

store.dispatch(addTodo('Learn Redux')); 

1.6 Reducer

Store 收到 Action 之後,必須給出一個新的 State,這樣 View 纔會發生變化。這種 State 的計算過程就叫作 Reducer。

Reducer 是一個函數,它接受 Action 和當前 State 做爲參數,返回一個新的 State。

const reducer = function (state, action) {  // ... return new_state; }; 

整個應用的初始狀態,能夠做爲 State 的默認值。下面是一個實際的例子。

const defaultState = 0; const reducer = (state = defaultState, action) => { switch (action.type) { case 'ADD': return state + action.payload; default: return state; } }; const state = reducer(1, { type: 'ADD', payload: 2 }); 

上面代碼中,reducer函數收到名爲ADD的 Action 之後,就返回一個新的 State,做爲加法的計算結果。其餘運算的邏輯(好比減法),也能夠根據 Action 的不一樣來實現。

實際應用中,Reducer 函數不用像上面這樣手動調用,store.dispatch方法會觸發 Reducer 的自動執行。爲此,Store 須要知道 Reducer 函數,作法就是在生成 Store 的時候,將 Reducer 傳入createStore方法。

import { createStore } from 'redux'; const store = createStore(reducer); 

上面代碼中,createStore接受 Reducer 做爲參數,生成一個新的 Store。之後每當store.dispatch發送過來一個新的 Action,就會自動調用 Reducer,獲得新的 State。

爲何這個函數叫作 Reducer 呢?由於它能夠做爲數組的reduce方法的參數。請看下面的例子,一系列 Action 對象按照順序做爲一個數組。

const actions = [ { type: 'ADD', payload: 0 }, { type: 'ADD', payload: 1 }, { type: 'ADD', payload: 2 } ]; const total = actions.reduce(reducer, 0); // 3 

上面代碼中,數組actions表示依次有三個 Action,分別是加0、加1和加2。數組的reduce方法接受 Reducer 函數做爲參數,就能夠直接獲得最終的狀態3

1.7 純函數

Reducer 函數最重要的特徵是,它是一個純函數。也就是說,只要是一樣的輸入,一定獲得一樣的輸出。

純函數是函數式編程的概念,必須遵照如下一些約束。

  • 不得改寫參數
  • 不能調用系統 I/O 的API
  • 不能調用Date.now()或者Math.random()等不純的方法,由於每次會獲得不同的結果

因爲 Reducer 是純函數,就能夠保證一樣的State,一定獲得一樣的 View。但也正由於這一點,Reducer 函數裏面不能改變 State,必須返回一個全新的對象,請參考下面的寫法。

 // State 是一個對象 function reducer(state, action) { return Object.assign({}, state, { thingToChange });  // 或者 return { ...state, ...newState }; }  // State 是一個數組 function reducer(state, action) { return [...state, newItem]; } 

最好把 State 對象設成只讀。你無法改變它,要獲得新的 State,惟一辦法就是生成一個新對象。這樣的好處是,任什麼時候候,與某個 View 對應的 State 老是一個不變的對象。

1.8 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(); 

2、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 }; }; 

3、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) 

4、工做流程

本節對 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); } 

5、實例:計數器

下面咱們來看一個最簡單的實例。

const Counter = ({ value }) => ( <h1>{value}</h1> ); const render = () => { ReactDOM.render( <Counter value={store.getState()}/>, document.getElementById('root') ); }; store.subscribe(render); render(); 

上面是一個簡單的計數器,惟一的做用就是把參數value的值,顯示在網頁上。Store 的監聽函數設置爲render,每次 State 的變化都會致使網頁從新渲染。

下面加入一點變化,爲Counter添加遞增和遞減的 Action。

 

const Counter = ({ value }) => ( <h1>{value}</h1> <button onClick={onIncrement}>+</button> <button onClick={onDecrement}>-</button> ); const reducer = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }; const store = createStore(reducer); const render = () => { ReactDOM.render( <Counter value={store.getState()} onIncrement={() => store.dispatch({type: 'INCREMENT'})} onDecrement={() => store.dispatch({type: 'DECREMENT'})} />, document.getElementById('root') ); }; render(); store.subscribe(render); 

第二部分

1、中間件的概念

爲了理解中間件,讓咱們站在框架做者的角度思考問題:若是要添加功能,你會在哪一個環節添加?

(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其餘功能,也承擔不了,由於理論上,純函數不能進行讀寫操做。

(2)View:與 State 一一對應,能夠看做 State 的視覺層,也不合適承擔其餘功能。

(3)Action:存放數據的對象,即消息的載體,只能被別人操做,本身不能進行任何操做。

想來想去,只有發送 Action 的這個步驟,即store.dispatch()方法,能夠添加功能。舉例來講,要添加日誌功能,把 Action 和 State 打印出來,能夠對store.dispatch進行以下改造。

let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); next(action); console.log('next state', store.getState()); } 

上面代碼中,對store.dispatch進行了重定義,在發送 Action 先後添加了打印功能。這就是中間件的雛形。

中間件就是一個函數,對store.dispatch方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其餘功能。

2、中間件的用法

本教程不涉及如何編寫中間件,由於經常使用的中間件都有現成的,只要引用別人寫好的模塊便可。好比,上一節的日誌中間件,就有現成的redux-logger模塊。這裏只介紹怎麼使用中間件。

import { applyMiddleware, createStore } from 'redux'; import createLogger from 'redux-logger'; const logger = createLogger(); const store = createStore( reducer, applyMiddleware(logger) ); 

上面代碼中,redux-logger提供一個生成器createLogger,能夠生成日誌中間件logger。而後,將它放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能加強。

這裏有兩點須要注意:

(1)createStore方法能夠接受整個應用的初始狀態做爲參數,那樣的話,applyMiddleware就是第三個參數了。

const store = createStore( reducer, initial_state, applyMiddleware(logger) ); 

(2)中間件的次序有講究。

const store = createStore( reducer, applyMiddleware(thunk, promise, logger) ); 

上面代碼中,applyMiddleware方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文檔。好比,logger就必定要放在最後,不然輸出結果會不正確。

3、applyMiddlewares()

看到這裏,你可能會問,applyMiddlewares這個方法究竟是幹什麼的?

它是 Redux 的原生方法,做用是將全部中間件組成一個數組,依次執行。下面是它的源碼。

export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return {...store, dispatch} } } 

上面代碼中,全部中間件被放進了一個數組chain,而後嵌套執行,最後執行store.dispatch。能夠看到,中間件內部(middlewareAPI)能夠拿到getStatedispatch這兩個方法。

4、異步操做的基本思路

理解了中間件之後,就能夠處理異步操做了。

同步操做只要發出一種 Action 便可,異步操做的差異是它要發出三種 Action。

  • 操做發起時的 Action
  • 操做成功時的 Action
  • 操做失敗時的 Action

以向服務器取出數據爲例,三種 Action 能夠有兩種不一樣的寫法。

 // 寫法一:名稱相同,參數不一樣 { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } }  // 寫法二:名稱不一樣 { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } } 

除了 Action 種類不一樣,異步操做的 State 也要進行改造,反映不一樣的操做狀態。下面是 State 的一個例子。

let state = {  // ... isFetching: true, didInvalidate: true, lastUpdated: 'xxxxxxx' }; 

上面代碼中,State 的屬性isFetching表示是否在抓取數據。didInvalidate表示數據是否過期,lastUpdated表示上一次更新時間。

如今,整個異步操做的思路就很清楚了。

  • 操做開始時,送出一個 Action,觸發 State 更新爲"正在操做"狀態,View 從新渲染
  • 操做結束後,再送出一個 Action,觸發 State 更新爲"操做結束"狀態,View 再一次從新渲染

5、redux-thunk 中間件

異步操做至少要送出兩個 Action:用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;如何才能在操做結束時,系統自動送出第二個 Action 呢?

奧妙就在 Action Creator 之中。

class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props dispatch(fetchPosts(selectedPost)) }  // ... 

上面代碼是一個異步組件的例子。加載成功後(componentDidMount方法),它送出了(dispatch方法)一個 Action,向服務器要求數據 fetchPosts(selectedSubreddit)。這裏的fetchPosts就是 Action Creator。

下面就是fetchPosts的代碼,關鍵之處就在裏面。

const fetchPosts = postTitle => (dispatch, getState) => { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(postTitle, json))); }; };  // 使用方法一 store.dispatch(fetchPosts('reactjs')); // 使用方法二 store.dispatch(fetchPosts('reactjs')).then(() => console.log(store.getState()) ); 

上面代碼中,fetchPosts是一個Action Creator(動做生成器),返回一個函數。這個函數執行後,先發出一個Action(requestPosts(postTitle)),而後進行異步操做。拿到結果後,先將結果轉成 JSON 格式,而後再發出一個 Action( receivePosts(postTitle, json))。

上面代碼中,有幾個地方須要注意。

(1)fetchPosts返回了一個函數,而普通的 Action Creator 默認返回一個對象。

(2)返回的函數的參數是dispatchgetState這兩個 Redux 方法,普通的 Action Creator 的參數是 Action 的內容。

(3)在返回的函數之中,先發出一個 Action(requestPosts(postTitle)),表示操做開始。

(4)異步操做結束以後,再發出一個 Action(receivePosts(postTitle, json)),表示操做結束。

這樣的處理,就解決了自動發送第二個 Action 的問題。可是,又帶來了一個新的問題,Action 是由store.dispatch方法發送的。而store.dispatch方法正常狀況下,參數只能是對象,不能是函數。

這時,就要使用中間件redux-thunk

import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducers';  // Note: this API requires redux@>=3.1.0 const store = createStore( reducer, applyMiddleware(thunk) ); 

上面代碼使用redux-thunk中間件,改造store.dispatch,使得後者能夠接受函數做爲參數。

所以,異步操做的第一種解決方案就是,寫出一個返回函數的 Action Creator,而後使用redux-thunk中間件改造store.dispatch

6、redux-promise 中間件

既然 Action Creator 能夠返回函數,固然也能夠返回其餘值。另外一種異步操做的解決方案,就是讓 Action Creator 返回一個 Promise 對象。

這就須要使用redux-promise中間件。

import { createStore, applyMiddleware } from 'redux'; import promiseMiddleware from 'redux-promise'; import reducer from './reducers'; const store = createStore( reducer, applyMiddleware(promiseMiddleware) ); 

這個中間件使得store.dispatch方法能夠接受 Promise 對象做爲參數。這時,Action Creator 有兩種寫法。寫法一,返回值是一個 Promise 對象。

const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: 'FETCH_POSTS', payload: response.json() }); }); 

寫法二,Action 對象的payload屬性是一個 Promise 對象。這須要從redux-actions模塊引入createAction方法,而且寫法也要變成下面這樣。

import { createAction } from 'redux-actions'; class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props  // 發出同步 Action dispatch(requestPosts(selectedPost));  // 發出異步 Action dispatch(createAction( 'FETCH_POSTS', fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) )); } 

上面代碼中,第二個dispatch方法發出的是異步 Action,只有等到操做結束,這個 Action 纔會實際發出。注意,createAction的第二個參數必須是一個 Promise 對象。

看一下redux-promise源碼,就會明白它內部是怎麼操做的。

export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; } 

從上面代碼能夠看出,若是 Action 自己是一個 Promise,它 resolve 之後的值應該是一個 Action 對象,會被dispatch方法送出(action.then(dispatch)),但 reject 之後不會有任何動做;若是 Action 對象的payload屬性是一個 Promise 對象,那麼不管 resolve 和 reject,dispatch方法都會發出 Action。

 

第三部分

1、UI 組件

React-Redux 將全部組件分紅兩大類:UI 組件(presentational component)和容器組件(container component)。

UI 組件有如下幾個特徵。

  • 只負責 UI 的呈現,不帶有任何業務邏輯
  • 沒有狀態(即不使用this.state這個變量)
  • 全部數據都由參數(this.props)提供
  • 不使用任何 Redux 的 API

下面就是一個 UI 組件的例子。

const Title = value => <h1>{value}</h1>; 

由於不含有狀態,UI 組件又稱爲"純組件",即它純函數同樣,純粹由參數決定它的值。

2、容器組件

容器組件的特徵偏偏相反。

  • 負責管理數據和業務邏輯,不負責 UI 的呈現
  • 帶有內部狀態
  • 使用 Redux 的 API

總之,只要記住一句話就能夠了:UI 組件負責 UI 的呈現,容器組件負責管理數據和邏輯。

你可能會問,若是一個組件既有 UI 又有業務邏輯,那怎麼辦?回答是,將它拆分紅下面的結構:外面是一個容器組件,裏面包了一個UI 組件。前者負責與外部的通訊,將數據傳給後者,由後者渲染出視圖。

React-Redux 規定,全部的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動生成。也就是說,用戶負責視覺層,狀態管理則是所有交給它。

3、connect()

React-Redux 提供connect方法,用於從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。

import { connect } from 'react-redux' const VisibleTodoList = connect()(TodoList); 

上面代碼中,TodoList是 UI 組件,VisibleTodoList就是由 React-Redux 經過connect方法自動生成的容器組件。

可是,由於沒有定義業務邏輯,上面這個容器組件毫無心義,只是 UI 組件的一個單純的包裝層。爲了定義業務邏輯,須要給出下面兩方面的信息。

(1)輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數

(2)輸出邏輯:用戶發出的動做如何變爲 Action 對象,從 UI 組件傳出去。

所以,connect方法的完整 API 以下。

import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) 

上面代碼中,connect方法接受兩個參數:mapStateToPropsmapDispatchToProps。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),後者負責輸出邏輯,即將用戶對 UI 組件的操做映射成 Action。

4、mapStateToProps()

mapStateToProps是一個函數。它的做用就是像它的名字那樣,創建一個從(外部的)state對象到(UI 組件的)props對象的映射關係。

做爲函數,mapStateToProps執行後應該返回一個對象,裏面的每個鍵值對就是一個映射。請看下面的例子。

const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } 

上面代碼中,mapStateToProps是一個函數,它接受state做爲參數,返回一個對象。這個對象有一個todos屬性,表明 UI 組件的同名參數,後面的getVisibleTodos也是一個函數,能夠從state算出 todos 的值。

下面就是getVisibleTodos的一個例子,用來算出todos

const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) default: throw new Error('Unknown filter: ' + filter) } } 

mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,從新計算 UI 組件的參數,從而觸發 UI 組件的從新渲染。

mapStateToProps的第一個參數老是state對象,還可使用第二個參數,表明容器組件的props對象。

 // 容器組件的代碼 // <FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } } 

使用ownProps做爲參數後,若是容器組件的參數發生變化,也會引起 UI 組件從新渲染。

connect方法能夠省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引發 UI 組件的更新。

5、mapDispatchToProps()

mapDispatchToPropsconnect函數的第二個參數,用來創建 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操做應該看成 Action,傳給 Store。它能夠是一個函數,也能夠是一個對象。

若是mapDispatchToProps是一個函數,會獲得dispatchownProps(容器組件的props對象)兩個參數。

const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; } 

從上面代碼能夠看到,mapDispatchToProps做爲函數,應該返回一個對象,該對象的每一個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。

若是mapDispatchToProps是一個對象,它的每一個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被看成 Action creator ,返回的 Action 會由 Redux 自動發出。舉例來講,上面的mapDispatchToProps寫成對象就是下面這樣。

const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; } 

6、<Provider> 組件

connect方法生成容器組件之後,須要讓容器組件拿到state對象,才能生成 UI 組件的參數。

一種解決方法是將state對象做爲參數,傳入容器組件。可是,這樣作比較麻煩,尤爲是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。

React-Redux 提供Provider組件,可讓容器組件拿到state

import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ) 

上面代碼中,Provider在根組件外面包了一層,這樣一來,App的全部子組件就默認均可以拿到state了。

它的原理是React組件的context屬性,請看源碼。

class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object } 

上面代碼中,store放在了上下文對象context上面。而後,子組件就能夠從context拿到store,代碼大體以下。

class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState();  // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object } 

React-Redux自動生成的容器組件的代碼,就相似上面這樣,從而拿到store

7、實例:計數器

咱們來看一個實例。下面是一個計數器組件,它是一個純的 UI 組件。

class Counter extends Component { render() { const { value, onIncreaseClick } = this.props return ( <div> <span>{value}</span> <button onClick={onIncreaseClick}>Increase</button> </div> ) } } 

上面代碼中,這個 UI 組件有兩個參數:valueonIncreaseClick。前者須要從state計算獲得,後者須要向外發出 Action。

接着,定義valuestate的映射,以及onIncreaseClickdispatch的映射。

function mapStateToProps(state) { return { value: state.count } } function mapDispatchToProps(dispatch) { return { onIncreaseClick: () => dispatch(increaseAction) } }  // Action Creator const increaseAction = { type: 'increase' } 

而後,使用connect方法生成容器組件。

const App = connect( mapStateToProps, mapDispatchToProps )(Counter) 

而後,定義這個組件的 Reducer。

 // Reducer function counter(state = { count: 0 }, action) { const count = state.count switch (action.type) { case 'increase': return { count: count + 1 } default: return state } } 

最後,生成store對象,並使用Provider在根組件外面包一層。

import { loadState, saveState } from './localStorage'; const persistedState = loadState(); const store = createStore( todoApp, persistedState ); store.subscribe(throttle(() => { saveState({ todos: store.getState().todos, }) }, 1000)) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); 

完整的代碼看這裏

8、React-Router 路由庫

使用React-Router的項目,與其餘項目沒有不一樣之處,也是使用ProviderRouter外面包一層,畢竟Provider的惟一功能就是傳入store對象。

const Root = ({ store }) => ( <Provider store={store}> <Router> <Route path="/" component={App} /> </Router> </Provider> ); 

 

【筆記】

相關文章
相關標籤/搜索