react這么熱門的框架也不介紹了,redux是一個單項數據流的小框架,當然不只配合react,它起初是為react而配的,現在面向所有了,比如ng-redux的項目。redux做為react的標准搭配,大有超越flux的勢頭。今天show一個例子來入門redux。源碼在此。 (本文默認你已有react基礎,es6基礎)
這個效果很簡單就是一個count計數,+++++的按鈕按一下就會+1;input里面寫什么,submit就會alert什么內容,這個demo很雜亂,是我修改的官方counter的例子,加了一些實驗的東西,比較適合熟悉redux這個東西。
大家都知道,react是由一個個組件構成的,每個組件都是與其他沒有關系的,數據的傳遞都是通過props。redux實現的單項數據流就是為react量身定做的。它維護了一個store,處理一些東西,業務邏輯,數據都在這個store里面,就把它看成一個操作數據的顯示數據的東西,這個東西把它當作props傳入react組件,然后就可以用來顯示和操作數據了。這個react組件和平常寫的react組件差不多,但是里面的處理數據是用store,也就是傳入的props處理。詳細的教程請戳redux中文api。
有興趣的同學可以看一下我的另一篇博文:redux源碼賞析
1.首先看一下主頁的html,很簡單,沒什么東西。里面這個ul其實沒啥用的,哈哈哈。其實就是一個div,里面的東西都讓react處理。引入一個bundle.js(webpack壓縮后的文件)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>react+redux</title> </head> <body> <div id="root"> <ul> <li>l1</li> <li>l2</li> <li>l3</li> <li>l4</li> <li>l5</li> </ul> </div> <script type="text/javascript" src="bundle.js"></script> </body> </html>
2.主要就是main.js里的代碼
import {render} from "react-dom"
import React from "react" import {Provider} from "react-redux" import configureStore from "./store/configureStore" import App from './containers/app.js' const store =configureStore(); render(( <Provider store={store}> <App /> </Provider> ),document.getElementById('root'));
我們看它做了什么。引入了render,React,Provider,和自己寫的配置store-configureStore,還有經過redux處理過的組件App。render只是es6里的寫法,也可以換成平常react的寫法,就不列出了。Provider也不用管,只知道redux就是這么寫組件就可以了,里面把通過自己創建的store傳入到react組件,然后把處理過的App組件扔在那里,就完了。主要我們看它的store怎么創建的,和App怎么處理的。
3.先看store怎么處理的。這里是/store/configureStore.js
import {applyMiddleware,createStore} from "redux";
import thunk from "redux-thunk"; import reducer from "../reducers/reducer.js" const createStoreWithMiddleware=applyMiddleware(thunk)(createStore) export default function configuerStore(initialStore){ const store=createStoreWithMiddleware(reducer,initialStore); return store; }
它引入了中間件thunk,具體的作用就是讓action可以通過函數的處理,這個中間件只有幾行。如果action是函數就執行它,交給下一個中間件,沒有了就繼續流程
export default function thunkMiddleware({ dispatch, getState }) { return next => action => typeof action === 'function' ? // action 居然是函數而不是 plain object? action(dispatch, getState) : //在中間件里消化掉,讓該函數控制 dispatch 時機 next(action); //否則調用 next 讓其他中間件處理其他類型的 action }
接下來,可以看到創建store的函數createStoreWithMiddleware(reducer,initialStore);傳入的reducer和initialStore,initialStore是初始的Store狀態,可以隨便取結構,比如本例的store只有一個count字段,用來存儲count數字。reducer是重要的一點,它是執行業務邏輯的地方,它是一個純函數,任何時候結果都是不變的(傳入相同的東西)。
4.reducer /reducers/reducer.js"
import {combineReducers} from "redux"
import { ADD,RED } from '../actions/action'
function counter(state=0,action){ switch(action.type){ case ADD: return state+1; case RED:return state-1; default:return state; } } const rootReducer=combineReducers({ counter }) export default rootReducer;
combineReducers也不用管,他是組合多個reducer的,用於拆分業務。action只是描述了有事情發生了這一事實,並沒有指明應用如何更新 state。而這正是 reducer 要做的事情。 reducer就是如何改變數據的一個東西,這個counter的函數就是一個reducer,它是通過看action的type的值來操作state的,如果action的type是ADD,state就+1,是RED就-1;要謹記 reducer 一定要保持純凈。只要傳入參數一樣,返回必須一樣。沒有特殊情況、沒有副作用,沒有 API 請求、沒有修改參數,單純執行計算。這個action又是什么?
5.action actions/action.js
export const ADD="ADD"
export const RED="RED" export function increment(){ return { type:ADD } } export function decrement(){ return { type:RED } } export function ince(){ return (dispatch,getState) =>{ //const {counter}=getState() dispatch(increment()); } } export function dece(){ return (dispatch,getState)=>{ //const {counter}=getState() dispatch(decrement()) } }
為了簡單,我把官網例子的action改了,就留下加減兩個action。它是什么呢?你如何改變數據就看它了,它定義的ADD,RED兩個常量,作為type,redux規定action必須有type屬性,別的它不管。redux提供的api只有幾個,其中有dispatch和getState,dispatch是唯一改變state的api,getState是得到當前state,所以dispatch(action)就是改變當前state的方法,我們想增加count,就是dispatch(increment());我們把它封裝成ince()傳遞到組件里,讓它可以直接改變state,也就是每次點擊+++++就執行一下ince()這樣就可以了。這里的ince()和dece()只是手動套了一層dispatch,其實在項目中帶有副作用的操作是在這里執行,而不是在reducer里面執行,包括執行異步 API 請求,它可以不純凈。為什么它返回一個函數而上面的action返回一個對象都能夠運行呢,就是因為上面的thunk middleware的作用了。
總結一下,這就是整個store了,它創建了一個store,傳入了reducer(怎么根據action數據),reducer里包含了action(改變什么數據,具體怎么改)。把這個store傳入組件App,就能通過props里面的方法改變count了。
6.經過redux修飾的組件
看main.js里面直接把封裝的App展現出來了,我們看它怎么處理的App /containers/app.js
import {bindActionCreators} from "redux"
import {connect} from "react-redux" import App from '../component/App.js' import *as Actions from '../actions/action' function mapStateToprops(state){ return { counter:state.counter } } function mapDispatchToProps(dispatch){ return bindActionCreators(Actions,dispatch) } export default connect(mapStateToprops,mapDispatchToProps)(App)
bindActionCreators,看一下它的源碼,它把所有的Action都封裝了。 bindActionCreator
把action裝上一層dispatch。
//將 actionCreator 跟 dispatch 綁定在一起 let bindActionCreator => (actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)); } function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { //如果是單個 actionCreator,綁定一詞 return bindActionCreator(actionCreators, dispatch); } //返回一個改造過的「函數組合」 return mapValues(actionCreators, actionCreator => bindActionCreator(actionCreator, dispatch) ) }
惟一使用 bindActionCreators
的場景是當你需要把 action creator 往下傳到一個組件上,卻不想讓這個組件覺察到 Redux 的存在,而且不希望把 Redux store 或dispatch傳給它。
這里的App組件就是普通react組件,修飾它的方法就是connect。我們看connect干了什么。
connect傳入兩個函數為參,第一個參數的名字mapStateToprops,也很清楚了,你需要把state里的什么放到props里,這里就只有counter一個字段。第二個mapDispatchToProps,把dispatch傳入到props,讓組件可以調用dispatch來改變數據的結構,然而有bindActionCreators,就不用dispatch了,而且我們還在action里封裝的ince(),里面就是dispatch增加的action,也不用dispatch了。
把數據,怎么修改數據傳到props里,然后就可以用了。
最后看一下App這個組件,怎么用數據和怎么修改數據呢?
7.APP /component/App.js
import {render} from "react-dom"
import React from "react" class App extends React.Component { constructor(props){ super(props); this.handleClick=this.handleClick.bind(this); this.onSubmit=this.onSubmit.bind(this); } handleClick(){ const {ince,dece,counter} = this.props; ince(); } onSubmit(event){ alert(this.refs.text.value); } render() { const {ince,dece,counter} = this.props; return( <form onSubmit={this.onSubmit}> <input ref='text' type="text" /> <div>{counter}</div><input type="button" onClick={this.handleClick} value="++++++++++" /> <button>submit</button> </form> ) } } class Bpp extends React.Component{ render(){ return ( <div>{this.props.fuk} </div>) } } class Cpp extends React.Component{ render(){ const {ince,dece,increment,decrement,counter} =this.props; const id=this.props.params.id return ( <div>this is cpp id為{id}</div>) } } export default App;
Bpp和Cpp沒有顯示出來,只是用作實驗,刪掉也沒事。(我只是忘記刪了)
這里寫組件是用es6的寫法寫的。es6封裝了原生js的prototype操作,然而它是個不完全的語法糖,因為在里面不能寫屬性,public property也不行。
引入了React,render相當於ReactDOM.render。
組件用extends關鍵字繼承React.Componet,其實還可以用React.createClass,其實是一樣的。后者還是有很多react開發者使用的,因為它比較方便,為啥說比較方便,后面再提。
看一下這個jsx,就是一個form表單,提交觸發submit事件,一個輸入框,一個+++++的按鈕,一個div里面是{counter},這樣顯示數據。
值得一提的是const {ince,dece,counter} = this.props;把props傳的東西拿出來。拿出來什么呢?數據和如何修改數據的東西。這里只拿出了ince和dece,他們是我們封裝的action,在里面dispatch了,你就直接用它操作數據就可以了。counter是我們的數據,在div里{counter}就顯示了。
onSubmit={this.onSubmit},submit執行自身定義的事件,彈出this.refs.text的值,也就是input的值。
還有注意的就是constructor里面的東西了,這個東西是在class繼承的時候執行的,super(props);是必有的,如果不寫也會自動給你加上,就是執行一遍父類的constructor。要注意的是
this.handleClick=this.handleClick.bind(this);
this.onSubmit=this.onSubmit.bind(this);
這兩個必須要綁定一下this,不然在這兩個函數里取不到this,這很坑的,而且還很麻煩。再加上es6的class功能並不完善,有的地方替代不了js的原型操作,這也就促成了很開發者用React.createClass寫,組件生命周期事件還可以直接用,自然是比較方便的。
總結,這就是整個例子的代碼了,
這里手繪了一幅圖,主程序分為創建store和修飾app兩部分,主程序main.js將他們弄到一塊就完成了react-redux的例子。里面用的一些api沒有詳細講解,詳情看上面給出的redux中文api的鏈接。
webpack里面沒有用任何復雜的工具,僅僅是用babel轉換了一下es6,其他就沒有什么了。