React 之 Redux

作者 糖一瓶 日期 2018-03-30
React 之 Redux

一、什么是redux?

之前写过react组件内部的state,react传参数是父亲传儿子,儿子再传他儿子这样一级一级的就跟办手续似的。超级麻烦还累。所以就用到了redux。

官网上说:Redux is a predictable JavaScript state container。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

提供了“数据的单向流动”:
组件 → action → reducer → 其他组件
redux就是全局数据,采用了dispatch才能改变数据的这样的一个机制。

不懂???不懂就用一用嘛。用一用就懂了。

二、react结合redux

1. 在react中使用redux要先安装两个依赖:

npm install --save redux
npm install --save react-redux

  • react-redux就是粘合剂,让redux可以和react共同使用的东西
    先来一份初始的目录结构:
    test
     |-node_modules
     |-www     
         |-app    
           |-main.js
           |-App.js
         |-index.html
     |-webpack.config.js
     |-package.json

2. 创建reducers文件夹,创建index.js文件,这个文件向外暴露一个纯函数:

目录改变如下:

    修改后如下:
    test
     |-node_modules
     |-www     
         |-app    
           |-main.js
           |-App.js
           |-reducers
              |-index.js
         |-index.html
     |-webpack.config.js
     |-package.json

reducers/index.js代码如下:

export default (state = {"v" : 100} , action) => {
    // 这个函数的第一个参数就是我们的数据了
    // 第二个参数是干什么用的往下看就知道了
    // 这个是必须写的!!
    return state;
}
  • 开头就说了,react传个数据跟办手续似的,所以我们就用redux将数据放到全局去,让所有组件都可以去调用他。而我现在创建的reducers文件夹,说白了就是我们存放数据的地方。

3.改变main.js 和 App.js

main.js中做的事情:
①引入redux
②创建store
③通过Provider分发者,将store分发给所有的组件

App.js中做的事:
①从react-redux包中引入了{connect}
②通过connect关联reducers中的数据
③用this.props.**使用数据
④通过this.props.**()事件发送dispatch指令

main.js代码如下:

    import React from "react";
    import ReactDOM from "react-dom";

    //引入createStore和Provider
    import {createStore} from "redux";
    import {Provider} from "react-redux";
    //引入子组件App
    import App from "./App.js";
    //引入我们放数据的reducers
    import reducer from "./reducers";

    // 创建store store就跟个传达室似的 将store传给组件之后
    // 组件中才有使用this.store.props.**传达指令的权限
    // 然后就可以通过store获取到与其关联子组件关联数据
    const store = createStore(reducer);

    ReactDOM.render(
        //通过Provider 将store传输给所有的组件 就是让所有的组件拥有了this.store.props这个技能
        <Provider store={store}>
            <App></App>
        </Provider>
        ,
        document.getElementById("app")
)

App.js代码如下:

import React from "react";

// 从react-redux包中引入了{connect},干什么用的?
// 直接翻到最下边看connect怎么用
import {connect} from "react-redux";

class App extends React.Component{
    constructor(){
        super();
    }

    render(){
        return <div>
        // 在这里用this.props.v 
        // 还记得我们在reducers文件夹中的index.js中暴露的纯函数不
        // 就是那里的数据 所以这里就是<h1>100</h1>
        // 这里应该是不能写注释的,用代码请删注释,下同
            <h1>{this.props.v}</h1>
            // 然后我们发送onClick事件,发送this.props.add()函数,接下来我们到最下边的connect中看
            <button onClick={()=>{
                this.props.add()
            }}>按我加1</button>
            // 这里发送了this.props.minus()函数
             <button onClick={()=>{
                this.props.minus()
            }}>按我减2</button>
        </div>
    }
}


// 通过connect与reducers中的数据产生关联
// 然后才能通过刚刚main.js分配过来的store使用this.store.props.**指令在render中使用数据
//然后我们现在就去render里看看 
// 注意默认暴露connect 一个组件中只能有一个暴露,所以App类前的暴露记得去掉!否则会报错!
// connect写法:export default connect()(这里是组件的名称App里就是App)
export default connect(
    // 这里是你要用到的state数据
    (state) => ({
        v : state.v
    }),
    // 我们在connect中发送dispatch指令,这个指令的作用是修改数据的,type,n是载荷,可以看到载荷可以多个type必须有!
    // 就是通过dispatch指令发送的载荷,可以在reducers中,就是放数据的地方,还记得不!!
    // 可以在reducers中通过action.**来调用,比如action.type就是"ADD"
    // action就是reducers中index.js里边的第二个参数,还有印象不!
    // 具体是咋回事?往下看往下看
    (dispatch) => ({
        add(){
            dispatch({"type" : "ADD"})
        },
        minus(){
            dispatch({"type" : "MINUS",n:"2"})
        },
    })
)(App);

4.数据的改变

我们不是发送了个dispatch吗,然后带了个type吗。有啥用呢???
我们回到reducers/index.js,并修改它,
reducers/index.js修改后代码如下:

export default (state = {"v" : 100} , action) => {
    // 当点击按我加1时发送this.props.add()函数,然后触发dispatch({"type" : "ADD"})
    // 通过action.type得到之后进行如下判断,return了{"v" : state.v + 1}

    // 当点击按我减2的发送this.props.minus()函数,dispatch({"type" : "MINUS",n:"2"})
    //通过action.type得到之后进行如下判断,return了{ "v": state.v - action.n} action.n就是2
    if(action.type == "ADD"){
        return {
            "v" : state.v + 1
        }
    } else if (action.type == "MINUS") {
        return {
            "v": state.v - action.n
        }
    }
    return state;
}

  • 现在感觉redux饶了好大一个弯,实际上是因为业务小。
  • 如果业务大了,真的简化了程序。
  • 而且你可在reducers里看到所有可能的数据改变 ,这就是最开始提到的提供可预测化的状态管理。

三、一点小改变

之前我组件中的state时提过,react编程必须养成的思维:
组件只有父子之间有关系,兄弟和兄弟之间没有任何关系!
兄弟和兄弟之间不需要考虑彼此对彼此造成的任何的影响,只需要考虑对父亲的影响即可。

而现在使用了redux后:
所有组件只对全局数据(store中的state)负责,不需要对其他组件负责。
如果要改变全局数据,请dispatch一个action。

四、redux套路小结

  1. 首先创建一个reducers文件夹 里边存数据
  2. 在main.js 中创建store通过将store传递给所有的组件
  3. 在组件中引入connect 并使用connect使组件与reducers里的数据产生关联 得到要用的数据
  4. 通过this.props.** 使用数据
  5. 发送事件,触发dispatch命令发送type,也可以携带多个载荷
  6. 在reducers通过对action.type的值判断来对数据进行修改

五、combineReducers和bindActionCreators

combineReducers和bindActionCreators会使文件结构再次复杂,但是体系更完整。没什么好怕的。
从上边可以看出来套路都是很简短的。

先说说为什么要用这俩东西。
在一个项目中会有许多组件,但是store只能有一个。不同的组件不可能数据都用一样的,或者累的一大堆。
所以虽然我们store不能拆,但是reducers是可以拆分的。
这样的好处就是让我们项目的数据结构更加清晰,这时候就用到了combineReducers。
同理,我们的dispatch也分门别类不是更好,于是就用到了bindActionCreators。
知道这些就可以开始下边的操作了。

抛开上边的文件结构,我们重来一份。

    同样先来一份初始的目录结构:
    test
     |-node_modules
     |-www     
         |-app    
           |-main.js
           |-App.js
         |-index.html
     |-webpack.config.js
     |-package.json

combineReducers拆分reducers

1.创建reducers

创建reducers文件,并创建index.js,counterReducer.js,shoppingReducer.js

    修改后的目录结构:
    test
     |-node_modules
     |-www     
         |-app    
           |-main.js
           |-App.js
           |-reducers
               |-index.js
               |-counterReducer.js
               |-shoppingReducer.js
         |-index.html
     |-webpack.config.js
     |-package.json

reducers/index.js代码如下:
这个时候,index.js不再是像上边一样的数据文件了
其实就变成了一个reducers中所有数据文件的一个中心管理处
都是固定的用法

// 引入combineReducers
import {combineReducers} from "redux";

// 引入数据
import counterReducer from "./counterReducer.js";
import shoppingReducer from "./shoppingReducer.js";

//数据管理中心 就相当于state={counterReducer,shoppingReducer}
export default combineReducers({
    counterReducer,
    shoppingReducer
});

reducers/counterReducer.js代码如下:
这里用了switch语句,跟if同理,别看不懂了

export default (state = { "v": 100 }, action) => {
    switch (action.type) {
        case "ADD": {
            return {
                "v": state.v + 1
            }
        }
    }
    return state;
}

reducers/counterReducer.js代码如下:

export default (state = { "price": 1000 }, action) => {
    switch (action.type) {
        case "UP": {
            return {
                "price": state.price + 1000
            }
        }
    }
    return state;
}

2.main.js同上没有改变

3.组件要改变:

App.js代码如下:

import React from "react";
import {connect} from "react-redux";

class App extends React.Component{
    constructor(){
        super();
    }

    render(){
        return <div>
            <h1>{this.props.v}</h1>
            <button onClick={()=>{
                this.props.add();
            }}>按我加1</button>
        </div>
    }
}

export default connect(
    //这里原本是state,state现在不是变成state={counterReducer,shoppingReducer}
    //所有这里是一种结构的写法,此时就可以调用counterReducer.js中的v
    //注意写法!调用shoppingReducer也是同理了。
    ({counterReducer}) => ({
        v : counterReducer.v
    }),
    (dispatch) => ({
        add(){
            dispatch({"type" : "ADD"})
        }
    })
)(App);

这就实现了reducers的拆分了。接下来就要拆dispatch发送的action了。

提炼aciton文件

具体操作:创建actions文件夹,创建actions/counter.js文件:

    修改后的目录结构:
    test
     |-node_modules
     |-www     
         |-app    
           |-main.js
           |-App.js
           |-reducers
               |-index.js
               |-counterReducer.js
               |-shoppingReducer.js
           |-actions
                  |-counter.js
                  |-shopping.js
         |-index.html
     |-webpack.config.js
     |-package.json

actions/counter.js:

export const add = () => ({"type" : "ADD"});            //action creator
export const minus = () => ({"type" : "MINUS"});        //action creator

actions/shopping.js:

export const add = () => ({"type" : "UP"});            //action creator
export const minus = () => ({"type" : "DOWN"});        //action creator
  • 这个文件向外暴露了两个函数,函数运行之后将返回action。返回action的函数我们称为action creator。

组件要改变写法(刻意的背一下就行了,都是繁文缛节,没必要去看API,更没必要研究机理):
App.js:

import React from "react";
import {connect} from "react-redux";
// 引入bindActionCreators
import {bindActionCreators} from "redux";
// 引入刚刚创建的action creator 叫conter
import * as counter from "./actions/counter.js";

class App extends React.Component{
    constructor(){
        super();
    }

    render(){
        return <div>
            <h1>
                {this.props.v}
            </h1>
            <button onClick={()=>{
                //这里的写法改变了,调用下边定义的counterCreators
                // 因为你counterCreators使用bindActionCreators绑定到了counter中
                // 如下调用里头的add就发送了({"type" : "ADD"});  看actions/counter.js:
                this.props.counterCreators.add();
            }}>点击我加1</button>

            <button onClick={()=>{
                //这里也是
                this.props.counterCreators.minus();
            }}>点击我减1</button>
        </div>
    }
}
export default connect(
    ({counterReducer})=>({
        v : counterReducer.v
    }),
    (dispatch) => ({
        // 这里定义dispatch counterCreators是自己定的名字 绑定到counter中
        // 固定写法,counter就是你自己定义actions下的文件counter.js 在上边引入的
        //如果想使用shopping里的action creator就把counter改成shopping
        counterCreators: bindActionCreators(counter , dispatch)
    })
)(App);

六、最后再来捋一遍

  1. 首先创建一个reducers文件夹 里边存数据 将数据分类 然后用combineReducers 在reducers/index.js下进行综合管理
  2. 在main.js 中创建store通过将store传递给所有的组件
  3. 在组件中引入connect 并使用connect使组件与reducers里的数据产生关联 得到要用的数据
  4. state变成了一个对象,存储着所有的数据文件,结构state,通过类似counterReducers.v的方式,引入自己要用的数据
  5. 通过类似this.counterReducers.props.** 使用数据 其实就像是有了一个命名空间
  6. 通过自定义一个方法名字counterCreators,用bindActionCreators绑定到要使用的actions下的相应文件
  7. 通过类似this.counterCreators.add()的方法发送dispatch指令
  8. 指令就会找到对应的actions/count.js下去找add,并发送action({“type” : “ADD”});
  9. 在reducers通过对action.type的值判断来对数据进行修改