使用Redux管理你的React應用

來源:互聯網
上載者:User

標籤:

React是最好的前端庫,因為其發源於世界上最好的後端語言架構。 ---信仰

4.0 will likely be the last major release. Use Redux instead. It‘s really great. —Flummox架構作者 acdliteAndrew Clark

為什麼使用React還需要使用別的架構來搭配?

React的核心是使用組件定義介面的表現,是一個View層的前端庫,那麼在使用React的時候我們通常還需要一套機制去管理組件與組件之間,組件與資料模型之間的通訊。

為什麼使用Redux?

Facebook官方提出了FLUX思想管理資料流,同時也給出了自己的實現來管理React應用。可是當我開啟FLUX的文檔時候,繁瑣的實現,又臭又長的文檔,實在難以讓我有使用它的慾望。幸好,社區中和我有類似想法的不在少數,github上也湧現了一批關於實現FLUX的架構,比較出名的有Redux,Reflux,Flummox。

其中Redux的簡單和有趣的編程體驗是最吸引我的地方。

  • 簡單。和其它的FLUX實現不一樣,Redux只有唯一的state樹,不管項目變的有多複雜,我也僅僅只需要管理一個State樹。可能你會有疑問,一個state樹就夠用了?這個state樹該有多大?別著急,Redux中的Reducer機制可以解決這個問題。

  • 有趣。忙於迭代項目的你,體會編程帶來的趣味是有多久沒有體會到了?瞧下面這張圖,右邊那個調試工具是啥?整個應用的action和state都這麼被輕鬆的管理了?行為還能被儲存,刪除,復原,重設?修改了代碼,頁面不重新整理也能產生變化?別開玩笑了,不行,世界那麼大,讓我去試試!

註:Redux開發調試工具:redux-devtools
React應用無重新整理儲存工具:hot-loader

不明真相的群眾,可能這裡需要我來安利一下Flux資料流的思想,看圖:
  ╔═════════╗       ╔════════╗       ╔═════════════════╗  ║ Actions ║──────>║ Stores ║──────>║ View Components ║  ╚═════════╝       ╚════════╝       ╚═════════════════╝       ^                                      │       └──────────────────────────────────────┘  注意:圖片僅僅是FLUX思想,而不是Facebook的實現。

大致的過程是這樣的,View層不能直接對state進行操作,而需要依賴Actions派髮指令來告知Store修改狀態,Store接收Actions指令後發生相應的改變,View層同時跟著Store的變化而變化。

舉個例子:A組件要使B組件發生變化。首先,A組件需要執行一個Action,告知綁定B組件的Store發生變化,Store接收到派發的指令後改變,那相應的B組件的視圖也就發生了改變。假如C,D,E,F組件綁定了和B組件相同的Store,那麼C,D,E,F也會跟著變化。

使用React和Redux開發一個小程式

為了更好的描述怎麼樣使用Redux管理React應用,我做了一個Manage Items的小例子。你可以在這裡找到全部的原始碼:https://github.com/matthew-sun/redux-example。

快速查看
1.git clone [email protected]:matthew-sun/redux-example.git2.npm install && npm start3.open localhost:3000
目錄結構
.+-- app|   +-- actions|       +-- index.js|   +-- components|       +-- content.js|       +-- footer.js|       +-- searchBar.js|   +-- constants|       +-- ActionTypes.js|   +-- containers|       +-- App.js|   +-- reducers|       +-- index.js|       +-- items.js|       +-- filter.js|   +-- utils|   +-- configureStore.js|   +-- index.js+-- css|   +-- pure.min.css+-- index.html
Index.js

在入口檔案中,我們需要把App和redux建立起聯絡。Provider是react-redux提供的組件,它的作用是把store和視圖綁定在了一起,這裡的Store就是那個唯一的State樹。當Store發生改變的時候,整個App就可以作出對應的變化。{() => }是聲明了一個返回的函數傳進Provider的props.children裡,這個方法將會在React的 0.14版本得到簡化。

/* app/index.js */import React from ‘react‘;import { Provider } from ‘react-redux‘;import App from ‘./containers/App‘;import configureStore from ‘./configureStore‘;const store = configureStore();React.render(    <div>        <Provider store={store}>            {() => <App /> }        </Provider>    </div>,    document.getElementById(‘app‘));
Constants

keyMirror這個方法非常的有用,它可以協助我們輕鬆建立與索引值key相等的常量。

/* app/constants/actionTypes.js */import keyMirror from ‘react/lib/keyMirror‘;export default keyMirror({    ADD_ITEM: null,    DELETE_ITEM: null,    DELETE_ALL: null,    FILTER_ITEM: null});// 等於// export const ADD_ITEM = ‘ADD_ITEM‘;// export const DELETE_ITEM = ‘DELETE_ITEM‘;// export const DELETE_ALL = ‘DELETE_ALL‘;// export const FILTER_ITEM = ‘FILTER_ITEM‘;
Actions

Action向store派髮指令,action 函數會返回一個帶有 type 屬性的 Javascript Plain Object,store將會根據不同的action.type來執行相應的方法。addItem函數的非同步作業我使用了一點小技巧,使用redux-thunk中介軟體去改變dispatch,dispatch是在View層中用bindActionCreators綁定的。使用這個改變的dispatch我們可以向store發送非同步指令。比如說,可以在action中放入向服務端的請求(ajax),也強烈推薦這樣去做。

/* app/actions/index.js */import { ADD_ITEM, DELETE_ITEM, DELETE_ALL, FILTER_ITEM } from ‘../constants/actionTypes‘;export function addItem(item) {    return dispatch => {       setTimeout(() => dispatch({type: ADD_ITEM}), 1000)    }}export function deleteItem(item, e) {    return {       type: DELETE_ITEM,       item    }}export function deleteAll() {    return {       type: DELETE_ALL    }}export function filterItem(e) {    let filterItem = e.target.value;    return {       type: FILTER_ITEM,       filterItem    }}
Reducers

Redux有且只有一個State狀態樹,為了避免這個狀態樹變得越來越複雜,Redux通過 Reducers來負責管理整個應用的State樹,而Reducers可以被分成一個個Reducer。

Reduce在javascript Array的方法中出現過,只是不太常用。簡單快速的用代碼範例來回顧一下:

  /* Array.prototype.reduce */var arr = [1,2,3,4];var initialValue = 5;var result = arr.reduce(function(previousValue, currentValue) {    return previousValue + currentValue}, initialValue)console.log(result)// 15// 該回呼函數的傳回值為累積結果,並且此傳回值在下一次調用該回呼函數時作為參數提供。// 整個函數執行的過程大致是這樣 ((((5+1)+2)+3)+4)

回到Redux中來看,整個的狀態就相當於從[初始狀態]merge一個[action.state]從而得到一個新的狀態,隨著action的不斷傳入,不斷的得到新的狀態的過程。(previousState, action) => newState,注意:任何情況下都不要改變previousState,因為這樣View層在比較State的改變時只需要簡單比較即可,而避免了深度迴圈比較。Reducer的資料結構我們可以用immutable-js,這樣我們在View層只需要react-immutable-render-mixin外掛程式就可以輕鬆的跳過更新那些state沒有發生改變的組件子樹。

/* app/reducers/items.js */import Immutable from ‘immutable‘;import { ADD_ITEM, DELETE_ITEM, DELETE_ALL } from ‘../constants/actionTypes‘;const initialItems = Immutable.List([1,2,3]);export default function items(state = initialItems, action) {    switch(action.type) {        case ADD_ITEM:            return state.push( state.size !=0 ? state.get(-1)+1 : 1 );        case DELETE_ITEM:             return state.delete( state.indexOf(action.item) );        case DELETE_ALL:            return state.clear();        default:            return state;    }}
串連reducers

Redux提供的combineReducers函數可以協助我們把reducer組合在一起,這樣我們就可以把Reducers拆分成一個個小的Reducer來管理Store了。

/* app/reducers/index.js */import { combineReducers } from ‘redux‘;import items from ‘./items‘;import filter from ‘./filter‘;const rootReducer = combineReducers({  items,  filter});export default rootReducer;
Middleware

在Redux中,Middleware 主要是負責改變Store中的dispatch方法,從而能處理不同類型的 action 輸入,得到最終的 Javascript Plain Object 形式的 action 對象。

以redux-thunk為例子:

/* redux-thunk */  export default function thunkMiddleware({ dispatch, getState }) {  return next =>      action =>        typeof action === ‘function’ ?          action(dispatch, getState) :          next(action);}

當ThunkMiddleware 判斷action傳入的是一個函數,就會為該thunk函數補齊dispatch和getState參數,否則,就調用next(action),給後續的Middleware(Middleware 外掛程式可以被綁定多個)得到使用dispatch的機會。

 /* app/configureStore.js */import { compose, createStore, applyMiddleware } from ‘redux‘;import thunk from ‘redux-thunk‘;import rootReducer from ‘./reducers‘;var buildStore = compose(applyMiddleware(thunk), createStore)export default function configureStore(initialState) {   return buildStore(rootReducer, initialState);}
UI

智能組件和木偶組件,因為本文主要是介紹Redux,對這個感興趣的同學可以看一下這篇文章Smart and Dumb Components。本項目中在結構上會把智能組件放在containers中,木偶組件放於components中。

containers

智能組件,會通過react-redux函數提供的connect函數把state和actions轉換為旗下木偶組件所需要的props。

/* app/containers/App.js */import React from ‘react‘;import SearchBar from ‘../components/searchBar‘;import Content from ‘../components/content‘;import Footer from ‘../components/footer‘;import { connect } from ‘react-redux‘;import ImmutableRenderMixin from ‘react-immutable-render-mixin‘;import * as ItemsActions from ‘../actions‘;import { bindActionCreators } from ‘redux‘;let App = React.createClass({     mixins: [ImmutableRenderMixin],     propTypes: {         items: React.PropTypes.object,         filter: React.PropTypes.string     },     render() {         let styles = {             width: ‘200px‘,             margin: ‘30px auto 0‘         }         const actions = this.props.actions;         return (             <div style={styles}>                 <h2>Manage Items</h2>                 <SearchBar filterItem={actions.filterItem}/>                 <Content items={this.props.items} filter={this.props.filter} deleteItem={actions.deleteItem}/>                 <Footer addItem={actions.addItem} deleteAll={actions.deleteAll}/>             </div>         )     } })export default connect(state => ({     items: state.items,     filter: state.filter}), dispatch => ({     actions: bindActionCreators(ItemsActions, dispatch)}))(App);
components

木偶組件,各司其職,沒有什麼關於actions和stores的依賴,拿出項目中也可獨立使用,甚至可以和別的actions,stores進行綁定。

  • SearchBar:尋找Item。
  • Content:控制Items的顯示,刪除一個Item。
  • Footer:新增Item,刪除全部Item。
調試工具

使用redux-devtools調試,為你在開發過程中帶來樂趣。

/* app/index.js */function renderDevTools(store) {  if (__DEBUG__) {    let {DevTools, DebugPanel, LogMonitor} = require(‘redux-devtools/lib/react‘);    return (      <DebugPanel top right bottom>        <DevTools store={store} monitor={LogMonitor} />      </DebugPanel>    );  }else {    return null;  }}React.render(    <div>        <Provider store={store}>            {() => <App /> }        </Provider>        {renderDevTools(store)}    </div>,  document.getElementById(‘app‘));/* app/configureStore.js */var buildStore;if(__DEBUG__) {  buildStore = compose(    applyMiddleware(thunk),    require(‘redux-devtools‘).devTools(),    require(‘redux-devtools‘).persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),    createStore  )}else {  buildStore = compose(applyMiddleware(thunk), createStore)}export default function configureStore(initialState) {  return buildStore(rootReducer, initialState);}

在你的代碼中加上上面的兩段代碼,運行npm run debug命令,就可以用調試工具來管理你的項目了。

延伸閱讀
  • Redux Document
  • Awesome-redux
寫在最後

剛接觸到Redux和React技術的時候,我幾乎是夜夜難以入眠的,技術革新帶來的新的思想總是不斷的刺激著我的大腦。非常建議你也能來試試Redux,體會我在開發中得到的這種幸福感。

如果有任何想要瞭解的,歡迎來我的github和我一起互動交流。?

使用Redux管理你的React應用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.