上一篇文章,很簡單的用代碼來講明瞭State,Action,Reducer,Store各自的意義和相互的聯繫以及在實際開發的應用。可是在現實開發環境用,咱們的需求不只僅上一篇文章那麼簡單的。爲了應付這些複雜的開發環境,咱們還須要用到如下這些東西:html
Action Creator / bindActionCreators:動態去生成Action。java
combineReducers:分割你的reducers,使每個reducer更有意義,以後再合併回來。node
applyMiddleware / createStoreWithMiddleware:往store裏面添加中間件。使得它的功能更增強大。react
很明顯,咱們將要學習的這三個功能,對應回上一篇文章的Action,reducer,store。你能夠認爲這是它們各自的一些擴展。npm
在上一篇文章咱們的需求很簡單。就是一個按鈕,點擊就讓數字遞增,那麼今天爲了學習新的東西,咱們就增長一些新的需求。咱們需求加兩個按鈕,一個按鈕就是點擊數字是+2,一個按鈕是切換數字顏色。那麼爲了更方便咱們的開發,咱們的開發目錄也應該修改一下。redux
主要寫代碼的仍是在app這個文件夾裏面,裏面的目錄分佈babel
component -->放置組件的app
index.jsdom
redux -->放置redux
action -->action相關的
types.js
creator.js
reducer -->reducer相關的
rootReducer.js
increaseReducer.js --> 處理增長按鈕用的
themeReducer.js --> 處理切換主題按鈕用的
store -->store相關的
store.js
index.js -->把組件和store connect在一塊兒生成一個新組件
main.js -->程序入口
在Redux之旅-1裏面,咱們建立了一個很簡單的例子:點擊按鈕遞增。那麼在更多的時候,咱們的開發需求是很複雜的。那麼增長一個點擊就+2的按鈕。這時候咱們要怎麼作?
還記得咱們上次定義的Action是這樣的:
let IncreaseAction = {type:'increase'}
其中字符串increase的意思就是增長,那麼咱們如今的需求裏面,一個點擊+2的按鈕,也符合increase這個意思。這時候的Action就不能像以前那樣,直接寫死。由於寫死了就沒辦法讓reducer知道,這個Action是+1仍是+2了!而須要引入一個新的概念:Action Creator
let IncreaseAction = (num = 1)=>{ return{ type:'increaseAction', num //看注1 } }
注1:這裏用到了ES6的新語法,加強字面量。具體看文章最後的註釋
上面就是一個Action Creator,聽起來很高大上的英文名詞,其實就是一個幫咱們返回Action的函數。那還記得Action的本質實際上是什麼嗎?一個咱們規定最了低限度要帶着type屬性的對象。那麼其實就很簡單了。Creator每次幫咱們生成type都是increaseAction,但num不一樣的Action出來。這樣,reducer就能分辨出咱們的這個Action要+1仍是+2
文件位置: app/redux/action/types.js
//爲了更加直觀,咱們把每一個Action的type屬性的值額外用一個文件去描述 export const INCREASE = 'increase' //增長數字的 export const THEME = 'theme' //切換主題的
文件位置: app/redux/action/creator.js
import * as types from './types' export let increaseAction = (num = 1)=>{ return { type:types.INCREASE, num } } export let themeAction = ()=>{ return { type:types.THEME, } }
OK!上面的代碼中,我還順便把切換主題的Action也寫了,雖然它不須要傳入參數去處理,可是爲了之後的拓展性,用Action Creator總比用寫死了的Action更佳,畢竟產品經理要改需求,誰也擋不住。
和Flux不一樣。Redux的store只有一個!只有一個store就意味着只有一個state。那麼咱們設計state的時候就很會糾結了。像如今這樣子:
let state = { count:0, //增長數字 theme:'#ffffff' //主題顏色 }
這並非一個理想的state。一個理想的state應該要設計得很純粹。不該該把無關得兩個東西交錯在一塊兒。由於,咱們須要分割開咱們的reducer。每一個不一樣的reducer下面僅僅處理本身擁有的state.
let CountState = { count:0, //增長數字 } let ThemeState = { theme:'#ffffff' //主題顏色 }
這樣的話,咱們的state就能夠設計得很小,而且很純粹。而做爲彙總,咱們會用combineReducers把這些reducers合併回來。實際上state仍是一大塊,可是在reducer的角度來看,每一個reducer都指示一小塊。
文件位置: app/redux/reducer/increaseReducer.js
//處理數字增長的reducer import * as types from '../action/types' let reducer = (state={count:0},action)=>{ let count = state.count switch(action.type){ case types.INCREASE: //注意這裏使用的action.num,明白是從哪裏來的嗎? return {count:count+action.num} break default: return state } } export default reducer
文件位置: app/redux/reducer/themeReducer.js
//處理主題的reducer import * as types from '../action/types' const writeColor = '#ffffff' const grayColor = '#cccccc' let reducer = (state={color:writeColor},action)=>{ let count = state.count switch(action.type){ case types.THEME: if(state.color == writeColor){ return {color:grayColor} } else { return {color:writeColor} } break default: return state } } export default reducer
文件位置: app/redux/reducer/rootReducer.js
//合併兩個reducer import { combineReducers } from 'redux' import Increase from './increaseReducer' import Theme from './themeReducer' const rootReducer = combineReducers({ Increase, Theme }) export default rootReducer
在這裏我並不打算深刻去描述中間件這種概念,若是你是作node開發的話,又恰好用上了KOA的話,你會對這個東西很熟識。那麼這裏我簡單的介紹一下什麼是中間件。
你可能認爲它是一個使用包含自定義功能的(可讓你包裝 store 的 dispatch 方法來達到你想要的功能),用來擴展 Redux 是一種方式。
在這裏,有兩個中間件是推薦使用的。
//用來打log的,開發時候用很方便 npm install --save-dev redux-logger //這個會在異步Action的時候用到,這篇文章沒有到,不細說。 npm install --save redux-thunk
注2:thunk,推薦閱讀阮一峯老師的Thunk 函數的含義和用法
文件位置: app/redux/store/store.js
'use strick' import {createStore,applyMiddleware} from 'redux' import createLogger from 'redux-logger' import rootReducer from '../reducer/rootReducer' /* 之前建立store的方式: let store = createStore(reducers) */ let createStoreWithMiddleware = applyMiddleware( createLogger(), )(createStore) let store = createStoreWithMiddleware(rootReducer) export default store
這裏面和之前的差很少,但又有一些細節上的東西,咱們須要注意的
文件位置: app/redux/index.js
'use strict' import React from 'react' //注意這裏引入:bindActionCreators import { bindActionCreators } from 'redux' import { Provider,connect } from 'react-redux' //這裏引入了組件 import Index from '../component/index' //引入了action creator 和 store。爲啥沒有引入reducer? //由於reducer在上面建立store的時候已經用到了 import * as actions from './action/creator' import store from './store/store' let {Component} = React /* mapStateToProps裏面須要注意的是,因爲咱們的reducer是合併起來的,所以咱們的state也是幾個state拼起來。至因而哪幾個state拼起來? 能夠看回去rootReducer.js裏面combineReducers的時候,裏面的對象名字就是這裏state的名字。 固然這裏的return能夠寫成:return {state}也沒所謂。可是爲了你們能認清這個state裏面有什麼東西,這裏寫的稍微複雜一點 */ let mapStateToProps = (state) =>{ //return {state} return { reduxState:{ Increase:state.Increase, Theme:state.Theme, } } } /* mapDispatchToProps裏面用到了bindActionCreators。關於bindActionCreators的做用看下面註釋3 */ let mapDispatchToProps = (dispatch) =>{ return{ reduxActions:bindActionCreators(actions,dispatch) } } let Content = connect(mapStateToProps,mapDispatchToProps)(Index) class App extends Component{ render(){ return <Provider store={store}><Content /></Provider> } } export default App
注3: bindActionCreators(actionCreators, dispatch):是把 action creators 轉成擁有同名 keys 的對象,而使用 dispatch 把每一個 action creator 包圍起來,這樣能夠直接調用它們。
關於redux的開發在上面已經完成了,如今進入的是組件的開發,這裏面更多的是知足例子而寫的。沒有太多的現實開發價值。可是咱們能夠在這裏面很好的觀察,咱們寫好的程序是怎樣工做的。
文件位置: app/component/index.js
'use strict' import React from 'react' let {Component} = React class Index extends Component{ constructor(props){ super(props) } //點擊增長按鈕事件 _addHandle(num){ /* 這裏面能夠想一下increaseAction的名字是怎麼來的,一樣下面主題切換按鈕事件的themeAction又是怎麼來的,代碼以後爲你揭祕。 */ let {increaseAction} = this.props.reduxActions increaseAction(num) } //主題切換按鈕事件 _ThemeHandle(){ let { themeAction } = this.props.reduxActions themeAction() } render(){ //這裏面輸出props看看裏面有什麼東西 //console.log(this.props) let { reduxState } = this.props return ( <div style={styles.circle}> <div style={{color:reduxState.Theme.color}}>{reduxState.Increase.count}</div> <div style={styles.btnTheme} onClick={this._ThemeHandle.bind(this)}>切換主題</div> <div style={styles.btnAddOne} onClick={()=>{this._addHandle(1)}}>+1</div> <div style={styles.btnAddTwo} onClick={()=>{this._addHandle(2)}}>+2</div> </div> ) } } //樣式定義,不用細看 const styles = { circle:{ width:'400px', height:'400px', position:'absolute', left:'50%', top:'50%', margin:'-200px 0 0 -200px', borderRadius:'50px', fontSize:'60px', color:'#545454', backgroundColor:'#ececec', lineHeight:'100px', textAlign:'center', }, btnAddOne:{ width:'100px', height:'50px', lineHeight:'50px', backgroundColor:'#fcfcfc', fontSize:'20px', position:'absolute', left:'40px', bottom:'10px', cursor:'pointer', }, btnAddTwo:{ width:'100px', height:'50px', lineHeight:'50px', backgroundColor:'#fcfcfc', fontSize:'20px', position:'absolute', right:'40px', bottom:'10px', cursor:'pointer', }, btnTheme:{ width:'80%', height:'50px', lineHeight:'50px', backgroundColor:'#fcfcfc', fontSize:'20px', position:'absolute', right:'10%', bottom:'100px', cursor:'pointer', } } export default Index
須要注意:在redux裏面action/creator/reducer/store各類定義太多了。有時候你很難找到這個東西的名稱是哪裏定義過來的感受。輸出組件的props的話,咱們獲得這麼一個東西
{ reduxAction:{ increaseAction:fn(), themeAction:fn(), }, reduxState:{ Increase:count, Theme:color, } }
reduxAction:來自redux/index.js裏面,咱們mapDispatchToProps的時候return 的一個對象時候本身定義的一個對象名字
increaseAction:fn()/themeAction:fn():由於bindActionCreators的轉化形成的和creator的同名函數。所以這兩個函數的名稱定義出自redux/action/creator.js
reduxState:來自redux/index.js裏面,咱們mapStateToProps的時候return 的一個對象時候本身定義的一個對象名字
Increase/Theme 也是在mapStateToProps裏面本身定義的。
這裏順便貼出入口文件
文件位置: app/main.js
'use strict' import 'babel-polyfill' import React from 'react'; import { render } from 'react-dom' import App from './redux/index'; render( <App />, document.getElementById('main') );
若是沒有作錯,咱們的程序能跑起來,如同下圖通常
而控制檯會在每次按鈕以後,會如同下圖同樣
和上一次說明state/Action/reducer/store的各類好比不一樣,此次須要說的分別是Action/reducer/store的一些擴展,爲了更好的寫程序,這些擴展是必不可少的,但會給新手一種把程序切割得很零散從而無從下手得感受。因此此次我更加偏重於說明,每個擴展存在得意義以及代碼得實現,但願你們能看懂。
在3.1的注1的時候,咱們說了一個ES6的新特性:字面量加強
ES6裏面咱們定義一個對象的時候
let c = 'abc' let obj = { a, //這個叫作鍵名簡寫 b(){ //這個是函數定義的簡寫 .... }, [c]:12 // 這個是自定義鍵值寫法,一個頗有趣的語法糖 }
等於號後面咱們叫作 字面量
以上代碼等價於如下
let obj = { a:a, b:function(){ .... }, abc:12 }