狀態管理是目前構建單頁應用中不可或缺的一環,也是值得花時間學習的知識點。React官方推薦咱們使用Redux來管理咱們的React應用,同時也提供了Redux的文檔來供咱們學習,中文版地址爲http://cn.redux.js.org/index.htmlcss
雖然官方文檔上說只需幾分鐘就能上手 Redux,可是我我的認爲即使你看個兩三天也可能上手不了,由於文檔裏面的知識點不只數量較多,並且還艱澀難懂,不結合一些實例來看很難用於實際項目中去。html
可是不要擔憂本身學不會,這不我就給你們帶來了這篇乾貨,也是我學習Redux的心得體驗。react
若是你對如何構建React單頁應用還不瞭解的能夠先移步個人上一篇文章《React 構建單頁應用方法與實例》。webpack
那麼下面我就將介紹如何利用Redux來管理你的React項目了,而這裏我主要教你構建的是基於React + Redux + React-Router的方法,這也是官方文檔裏介紹的比較少可是項目中卻必備的知識點。git
首先,一個基於React + Redux + React-Router的項目目錄能夠按照我下方的圖片來構建:github
其中assets目錄用於存放項目的靜態資源,如css/圖片等,src目錄則用於存放React的組件資源。web
在webpack的配置項中,咱們須要一個或多個入口文件,這裏我就不展現關於package.json及webpack.config.js的文件配置,最後我會提供整個項目的下載連接供你們參考。這裏我主要介紹下入口文件index.js的配置說明。json
import React from 'react' // 引入React import { render } from 'react-dom' // 引入render方法 import { Provider } from 'react-redux' // 利用Provider可使咱們的 store 能爲下面的組件所用 import { Router, browserHistory } from 'react-router' // Browser history 是由 React Router 建立瀏覽器應用推薦的 history import { syncHistoryWithStore } from 'react-router-redux' // 利用react-router-redux提供的syncHistoryWithStore咱們能夠結合store同步導航事件 import finalCreateStore from './src/store/configureStore' //引入加強後的store import DevTools from './src/containers/DevTools' // 引入Redux調試工具DevTools import reducer from './src/reducers' // 引入reducers集合 import routes from './src/routes' // 引入路由配置 import './assets/css/bootstrap.min.css' // 引入樣式文件 // 給加強後的store傳入reducer const store = finalCreateStore(reducer) // 建立一個加強版的history來結合store同步導航事件 const history = syncHistoryWithStore(browserHistory, store) render( {/* 利用Provider包裹頁面 */} <Provider store={store}> <div> {/* 渲染根路由 */} <Router history={history} routes={routes} /> {/* 渲染調試組件 */} <DevTools /> </div> </Provider>, document.getElementById('mount') )
在入口文件中咱們儘可能只須要保留基本的東西,其他的配置代碼咱們能夠放到相應的配置文件中去,好比路由、reducers及store的配置等。這裏我都把它們放置到了獨立的js中,只在入口文件中經過import引入,這樣管理和維護起來會很是方便,但也會相應增長理解的難度,然而一旦上手就會很容易。那麼接下來咱們再來看下store配置吧。redux
import thunk from 'redux-thunk' // redux-thunk 支持 dispatch function,而且能夠異步調用它 import createLogger from 'redux-logger' // 利用redux-logger打印日誌 import { createStore, applyMiddleware, compose } from 'redux' // 引入redux createStore、中間件及compose import DevTools from '../containers/DevTools' // 引入DevTools調試組件 // 調用日誌打印方法 const loggerMiddleware = createLogger() // 建立一箇中間件集合 const middleware = [thunk, loggerMiddleware] // 利用compose加強store,這個 store 與 applyMiddleware 和 redux-devtools 一塊兒使用 const finalCreateStore = compose( applyMiddleware(...middleware), DevTools.instrument(), )(createStore) export default finalCreateStore
這裏咱們須要瞭解中間件(Middleware)的概念。middleware 是指能夠被嵌入在框架接收請求到產生響應過程之中的代碼,你能夠在一個項目中使用多個獨立的第三方 middleware,如上面的redux-thunk和redux-logger。詳細資料請參考官方文檔:http://cn.redux.js.org/docs/advanced/Middleware.html。bootstrap
上面的入口文件配置中咱們把路由配置部分單獨放到了routes.js的文件中,這裏咱們就來看下其配置:
import React from 'react' // 引入react import { Route, IndexRoute } from 'react-router' // 引入react路由 import { App, Home, Foo, Bar, Antd } from './containers' // 引入各容器組件 export default ( <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="index" component={Home}/> <Route path="foo" component={Foo}/> <Route path="bar" component={Bar}/> <Route path="antd" component={Antd}/> </Route> )
這裏的路由配置和不使用redux時候是同樣的,惟一須要瞭解的是容器組件和展現組件的概念。上面配置文件中的路由加載的組件均可以認爲是容器組件。
顧名思義,展現組件包含在容器組件中,只用做頁面展現,不會定義數據如何讀取如何改變,只經過this.props接受數據和回調函數;
而容器組件中包含各展現組件的數據,即Props,它們爲展現組件或其餘組件提供數據和方法。
咱們應該把它們放在不一樣的文件夾中,以示區別,如上面「項目目錄」中的containers和components文件夾分別存放容器組件和展現組件。具體說明能夠參考文章:http://www.jianshu.com/p/6fa2b21f5df3。
import React, { Component } from 'react' // 引入React import { Link } from 'react-router' // 引入Link處理導航跳轉 export default class App extends Component { render() { return( <div> <nav className="navbar navbar-default"> <div className="container-fluid"> <div className="navbar-header"> <span className="navbar-brand" href="#"> <Link to="/">Redux</Link> </span> </div> <ul className="nav navbar-nav"> <li> <Link to="/index" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>計數器</Link> </li> <li> <Link to="/foo" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>靜態數據</Link> </li> <li> <Link to="/bar" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>動態數據</Link> </li> <li> <Link to="/antd" activeStyle={{color: '#555', backgroundColor: '#e7e7e7'}}>結合antd</Link> </li> </ul> </div> </nav> <div className="panel panel-default"> <div className="panel-body"> { this.props.children } </div> </div> </div> ) } }
整個根組件App.js主要渲染了整個應用的導航和可變區域,這其實和Redux沒有關係。須要注意的是to中的URL地址須要和routes.js中的path地址名稱一致。
寫到這裏尚未介紹Redux中的Action及Reducer的配置,那麼接下來就來介紹下。
import { INCREASE, DECREASE, GETSUCCESS, REFRESHDATA } from '../constants' // 引入action類型名常量 import 'whatwg-fetch' // 能夠引入fetch來進行Ajax // 這裏的方法返回一個action對象 export const increase = n => { return { type: INCREASE, amount: n } } export const decrease = n => { return { type: DECREASE, amount: n } } export const refreshData = () => { return { type: REFRESHDATA } } export const getSuccess = (json) => { return { type: GETSUCCESS, json } } function fetchPosts() { return dispatch => { return fetch('data.json') .then((res) => { console.log(res.status); return res.json() }) .then((data) => { dispatch(getSuccess(data)) }) .catch((e) => { console.log(e.message) }) } } // 這裏的方法返回一個函數進行異步操做 export function fetchPostsIfNeeded() { // 注意這個函數也接收了 getState() 方法 // 它讓你選擇接下來 dispatch 什麼 return (dispatch, getState) => { return dispatch(fetchPosts()) } }
上面返回一個action對象的方法叫作「action 建立函數」,它就是生成action的方法,也是store數據的惟一來源。
上面返回一個函數的方法叫作「異步action」,這裏使用的是Redux Thunk middleware,要引入redux-thunk這個專門的庫才能使用,這樣咱們就能夠實現異步Ajax請求改變狀態等功能了。
// reducers/count.js import { INCREASE, DECREASE, GETSUCCESS, REFRESHDATA } from '../constants' // 引入action類型常量名 // 初始化state數據 const initialState = { number: 1, lists: [ {text: '整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。'}, {text: '唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。'}, {text: '爲了描述 action 如何改變 state tree ,你須要編寫 reducers。'}, {text: '就是這樣,如今你應該明白 Redux 是怎麼回事了。'} ], data: [] } // 經過dispatch action進入 export default function update(state = initialState, action) { // 根據不一樣的action type進行state的更新 switch(action.type) { case INCREASE: return Object.assign({}, state, { number: state.number + action.amount }) break case DECREASE: return Object.assign({}, state, { number: state.number - action.amount }) break case GETSUCCESS: return Object.assign({}, state, { data: action.json }) case REFRESHDATA: return Object.assign({}, state, { data: [] }) default: return state } }
// reducers/index.js import { combineReducers } from 'redux' // 利用combineReducers 合併reducers import { routerReducer } from 'react-router-redux' // 將routerReducer一塊兒合併管理 import update from './count' // 引入update這個reducer export default combineReducers({ update, routing: routerReducer })
這裏咱們主要須要瞭解如何經過combineReducers來合併reducers,同時在進入reducer方法後咱們必須返回一個state的處理結果來更新state狀態,不然會報錯。還需注意的是在合併reducers的時候,須要加上routerReducer這個由「react-router-redux」提供的reducer來管理路由的狀態更新。
上文提到了容器組件和展現組件的區別和含義,這裏咱們須要在容器組件使用connect來搭配Redux來進行狀態管理,這是很關鍵的一步。
import React, { Component, PropTypes } from 'react' // 引入React import { connect } from 'react-redux' // 引入connect import List from '../components/List' // 引入展現組件List export default class Foo extends Component { render() { // 經過this.props獲取到lists的值 const { lists } = this.props return( <div> <ul className="list-group"> {將值傳入展現組件} { lists.map((e, index) => <List text={e.text} key={index}></List> )} </ul> </div> ) } } // 驗證組件中的參數類型 Foo.propTypes = { lists: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired }).isRequired).isRequired } // 獲取state中的lists值 const getList = state => { return { lists: state.update.lists } } // 利用connect將組件與Redux綁定起來 export default connect(getList)(Foo)
在容器組件中咱們須要獲取state中的初始狀態的時候,咱們須要使用connect。任何一個從 connect() 包裝好的組件均可以獲得一個 dispatch 方法做爲組件的 props,以及獲得全局 state 中所需的任何內容。connect() 的惟一參數是 selector。此方法能夠從 Redux store 接收到全局的 state,而後返回組件中須要的 props。詳資料請參考文檔:http://cn.redux.js.org/docs/basics/UsageWithReact.html。
上面的容器組件中引入了一個展現組件List,咱們來看下它的代碼:
import React, { Component, PropTypes } from 'react' export default class List extends Component { render() { return( <li className="list-group-item">{this.props.text}</li> ) } } List.propTypes = { text: PropTypes.string.isRequired }
從中咱們能夠發現,展現組件沒有connect的方法,數據是經過this.props來獲取的,這樣的方式可以是數據的變化清晰可查,便於管理和維護。
最後咱們來看下這個demo:
整個demo的代碼我都上傳到了個人github,須要的童鞋能夠訪問:https://github.com/luozhihao/redux-basic-example下載。
Redux的知識點繁多,這裏只作了大概的介紹,剩下的還須要本身不斷的摸索和實踐。但願本文可以幫助你瞭解利用redux構建項目的大致流程。
此致敬禮
本文地址:http://www.javashuo.com/article/p-maujsphv-y.html
博客園:http://www.cnblogs.com/luozhihao/p/5660496.html