Redux之旅-2

Redux之旅-2

时间:2016.4.22-11:24
做者:三月懒驴
入门配置文章:连接
Redux之旅-1:连接javascript

1. 前言

上一篇文章,很简单的用代码来讲明了State,Action,Reducer,Store各自的意义和相互的联系以及在实际开发的应用。可是在现实开发环境用,咱们的需求不只仅上一篇文章那么简单的。为了应付这些复杂的开发环境,咱们还须要用到如下这些东西:html

  • Action Creator / bindActionCreators:动态去生成Action。java

  • combineReducers:分割你的reducers,使每个reducer更有意义,以后再合并回来。node

  • applyMiddleware / createStoreWithMiddleware:往store里面添加中间件。使得它的功能更增强大。react

很明显,咱们将要学习的这三个功能,对应回上一篇文章的Action,reducer,store。你能够认为这是它们各自的一些扩展。npm

2. 新的需求

在上一篇文章咱们的需求很简单。就是一个按钮,点击就让数字递增,那么今天为了学习新的东西,咱们就增长一些新的需求。咱们需求加两个按钮,一个按钮就是点击数字是+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 -->程序入口

3. Action Creator

3.1 为何须要Creator

在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

3.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更佳,毕竟产品经理要改需求,谁也挡不住。

4. combineReducers

4.1 分割和合并

和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都指示一小块。

4.2 代码实现

文件位置: 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

5. Middleware

5.1 什么叫作中间件

在这里我并不打算深刻去描述中间件这种概念,若是你是作node开发的话,又恰好用上了KOA的话,你会对这个东西很熟识。那么这里我简单的介绍一下什么是中间件。

你可能认为它是一个使用包含自定义功能的(可让你包装 store 的 dispatch 方法来达到你想要的功能),用来扩展 Redux 是一种方式。

在这里,有两个中间件是推荐使用的。

//用来打log的,开发时候用很方便
npm install --save-dev redux-logger  
//这个会在异步Action的时候用到,这篇文章没有到,不细说。
npm install --save redux-thunk

注2:thunk,推荐阅读阮一峰老师的Thunk 函数的含义和用法

5.2 代码实现

文件位置: 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

6. 链接组件和redux

这里面和之前的差很少,但又有一些细节上的东西,咱们须要注意的

文件位置: 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 包围起来,这样能够直接调用它们。

7. 组件开发

关于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')
);

若是没有作错,咱们的程序能跑起来,如同下图通常

效果图-1

而控制台会在每次按钮以后,会如同下图同样

效果图-2

8. 总结

和上一次说明state/Action/reducer/store的各类好比不一样,此次须要说的分别是Action/reducer/store的一些扩展,为了更好的写程序,这些扩展是必不可少的,但会给新手一种把程序切割得很零散从而无从下手得感受。因此此次我更加偏重于说明,每个扩展存在得意义以及代码得实现,但愿你们能看懂。

9.一些ES6的小知识

在3.1的注1的时候,咱们说了一个ES6的新特性:字面量加强
ES6里面咱们定义一个对象的时候

let c = 'abc'
let obj = {
   a,   //这个叫作键名简写
   b(){   
       //这个是函数定义的简写
       .... 
   },
   [c]:12   // 这个是自定义键值写法,一个颇有趣的语法糖
}

等于号后面咱们叫作 字面量
以上代码等价于如下

let obj = {
   a:a,    
   b:function(){   
      ....
   },
   abc:12    
}
相关文章
相关标签/搜索