zoukankan      html  css  js  c++  java
  • 前端笔记之React(五)Redux深入浅出

    一、Redux整体感知

    ReduxJavaScript状态管理容器,提供了可被预测状态的状态管理容器。来自于Flux思想,Facebook基于Flux思想,在2015年推出Redux库。

    中文网站:http://www.redux.org.cn/

    官方githttps://github.com/reduxjs/redux

    首先要引redux.js包,这个包提供了Redux对象,这个对象可以调用Redux.createStore()方法。

    <body>
        <h1 id="info"></h1>
        <button id="btn1">加</button>
        <button id="btn2">减</button>
        <button id="btn3">乘</button>
        <button id="btn4">除</button>
    
        <script type="text/javascript" src="redux.min.js"></script>
        <script type="text/javascript">
            //这是一个 reducer,形式为 (state, action) => state 的纯函数。
            //纯函数:函数内部,不改变传入的参数,只return新值。
            //描述了 action 如何把 state 转变成下一个 state。
            const reducer = (state = {"v" : 10} , action)=>{
                if(action.type == "ADD"){
                    return {"v" : state.v + 1};
                }else if(action.type == "MINUS"){
                    return {"v" : state.v - 1};
                }else if(action.type == "CHENG2"){
                    return {"v" : state.v * 2}
                }
                 return state;
            }
            //创建 Redux store 来存放应用的状态。
            //store翻译为“仓库”,这是一个存放数据并且可以操作数据的东西
            const store = Redux.createStore(reducer);
    
            //创建一个视图函数,并且将store显示到视图上。
            const render = ()=>{
                //store的getState()方法可以得到仓库中的数据
                document.getElementById("info").innerHTML = store.getState().v;
            }
            render(); //调用render函数
            //要将store注册到视图,这样的话,当store中的数据变化候,就能自动调用render函数
            store.subscribe(render);
    
            // 应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。
            // 惟一改变state的办法是触发action,一个描述发生什么的对象。
            // 为了描述 action 如何改变 state 树,你需要编写 reducers。
            document.getElementById("btn1").onclick = function(){
                // 改变内部 state 惟一方法是 dispatch 一个 action,type表示要做的动作
                store.dispatch({"type":"ADD"})
            }
            document.getElementById("btn2").onclick = function(){
                store.dispatch({"type":"MINUS"})
            }
            document.getElementById("btn3").onclick = function(){
                store.dispatch({"type":"CHENG"})
            }
        </script>
    </body>

    创建一个叫做reducer的函数,reducer中“维持”一个量,叫state,初始值是{"v" : 0}

    这个函数你必须知道两点:

    1)它是一个纯函数,在函数内部不改变{"v" : 0}的,不改变state对象的,只是返回了新的state

    const reducer = (state = {"v" : 0} , action)=>{
        if(action.type == "ADD"){
            return {"v" : state.v + 1};
        }else if(action.type == "MINUS"){
            return {"v" : state.v - 1};
        }
        return state;
    }

    2)这个函数提供了可被预测的功能。

    reducer中的state,就像被关进了保险箱。任何对这个state的变化,只能是通过dispatch一个action来改变的。换句话说,只有dispatch一个action才能改变这个保险箱中的数据

    reducer函数中,用if语句来表示对state可能发生变化的罗列,只有罗列在楼层里面的改变,才会发生:

    if(action.type==""){
        return 新的state
    }else if(action.type ==""){
        return 新的state
    }else if(action.type ==""){
        return 新的state
    }
    示例代码

    创建store仓库,这个仓库创建时需要提供reducer,所以可以认为store就是reducerreducer就是store

    const store = Redux.createStore(reducer);

    reducer是一个纯函数,而store提供了三个方法:

     store.subscribe() 注册到视图

     store.getState() 得到数据

     store.dispatch() 发送action

     

    学习reduxreact结合,到时候就不用注册到视图。

    然后创建监听:

    document.getElementById("btn1").onclick = function(){
         store.dispatch({"type":"ADD"})
    }
    document.getElementById("btn2").onclick = function(){
         store.dispatch({"type":"MINUS"})
    }
    document.getElementById("btn3").onclick = function(){
         store.dispatch({"type":"CHENG"})
    }
    示例代码

    点击按钮,storedispatch出一个action。所谓的action就是一个JSON,这个JSON必须有type属性,值为大写字母。这个action没有任何意义,比如{"type":"ADD"},但reducer认识这个action,可以产生变化!

    案例2,添加载荷:

    <body>
        <h1 id="info"></h1>
        <button id="btn1">加</button>
    
        <input type="text" id="txt">
    <button id="btn2">加输入的</button>
    
        <script type="text/javascript" src="redux.min.js"></script>
        <script type="text/javascript">
            const reducer = (state = {"v" : 10}, {type, data})=>{
                if(type == "ADD"){
                return {"v" : state.v + action.data}
              return {"v" : state.v + data}
                }
                return state;
            }
            const store = Redux.createStore(reducer);
            const render = ()=>{
                document.getElementById("info").innerHTML = store.getState().v;
            }
            render();
            store.subscribe(render);
    
        document.getElementById("btn1").onclick = function(){
            store.dispatch({ type: 'ADD', data:100});
        }
    
            document.getElementById("btn2").onclick = function(){
                var a = parseInt(document.getElementById("txt").value);
                //载荷payload。
                store.dispatch({"type":"ADD" , data:a});
            }
        </script>
    </body>

    唯一可以改变state的方式dispatch一个action

    案例3:添加数组

    <body>
        <div id="box">
            <p><input type="text" id="name"></p>
            <p><input type="text" id="age"></p>
            <button id="btn">增加</button>
            <ul id="List">
    
            </ul>
        </div>
    
        <script type="text/javascript" src="redux.min.js"></script>
        <script type="text/javascript">
            //初始数据
            const initObj = {
                "arr" : [
                    {"name" : "小明" , "age" : 12},
                    {"name" : "小红" , "age" : 13},
                    {"name" : "小强" , "age" : 14}
                ]
            };
            //reducer函数
            const reducer = (state = initObj, {type , name , age}) => {
                if(type == "ADD_STUDENT"){
                    //不能push改变传入的原数组,所以要返回新数组
                    return {
                        "arr" : [
                            {name , age} ,
                            ...state.arr
                        ]
                    }
                }
                return state;
            }
            //创建store
            const store = Redux.createStore(reducer);
            //视图函数
            const render = function(){
                //清空ul
                document.getElementById("List").innerHTML = "";
                //创建li
                for(var i = 0 ; i < store.getState().arr.length ; i++){
                    var li = document.createElement("li");
                var storeArr = store.getState().arr[i]
                    li.innerHTML = storeArr.name + storeArr.age+"岁"
                    document.getElementById("List").appendChild(li);
                }
            }
            render();//运行视图函数
            store.subscribe(render);//注册到视图
            
            document.getElementById("btn").onclick = function(){
                var name = document.getElementById("name").value;
                var age = document.getElementById("age").value;
                //发出Action,这是唯一能够改变store的途径
                store.dispatch({"type" : "ADD_STUDENT", name, age })
            }
        </script>
    </body>

    案例4,加深练习:

    <body>
        <h1 id="info"></h1>
        <h1 id="info2"></h1>
        <button id="btn">+</button>
    
        <script type="text/javascript" src="redux.min.js"></script>
        <script type="text/javascript">
            var initObj = {
                "a" : {
                    "b" : {
                        "c" : {
                            "v" : 10
                        }
                    },
                    "m" : 8
                }
            }
            const reducer = (state = initObj , action) => {
                if(action.type == "ADD"){
                    return {
                        ...state, 
                        "a" : {
                            ...state.a ,
                            "b" : {
                                ...state.a.b,
                                "c" : {
                                    ...state.a.b.c ,
                                    "v" : state.a.b.c.v + 1
                                }
                            }
                        }
                    }
                }
                return state;
            }
            const store = Redux.createStore(reducer);
            const render = ()=>{
                document.getElementById("info").innerHTML = store.getState().a.b.c.v;
                document.getElementById("info2").innerHTML = store.getState().a.m;
            }
            render();
            store.subscribe(render);
    
            document.getElementById("btn").onclick = function(){
                store.dispatch({"type" : "ADD"});
            }
        </script>
    </body>

    二、ReduxReact进行结合开发

    React开发的时候使用Redux可预测状态容器,要装两个新的依赖:

     redux:提供createStorecombineReducersbindActionCreators等功能

     react-redux:只提供两个东西,<Provider>组件、connect()函数。

    安装两个依赖:

    npm install --save redux react-redux

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

    reducers/index.js

    export default (state = { v : 10}, action) => {
        return state;
    }

    main.js入口文件:

    import React from "react";
    import ReactDOM from "react-dom";
    import {createStore} from "redux";
    import App from "./App.js";
    import reducer from "./reducers";  //reducer函数
    
    //创建 Redux store 来存放应用的状态。
    const store = createStore(reducer);
    
    ReactDOM.render(
        <App></App>,
        document.getElementById("app")
    );

    有两个问题:

    如何让组件能够访问到store中的数据?

    如果让store中的数据改变的时候,能够自动更新组件的视图

    react-redux解决了这两个问题。

    main.js中创建<Provider>组件,它提供的是一个顶层容器的作用,实现store的上下文传递,是让store能够“注入”到所有组件

    react-redux提供Provider组件,可以让容器组件拿到state

    Provider在根组件外面包一层,这样一来,App所有的子组件就默认都拿到了state了。

    原理是React组件的context属性,就是将store这个对象放到上下文(context)中

    import React from "react";
    import ReactDOM from "react-dom";
    import {createStore} from "redux";
    import {Provider} from "react-redux";
    import App from "./App.js";
    import reducer from "./reducers";
    
    //创建store仓库
    const store = createStore(reducer);
    
    ReactDOM.render(
        <Provider store={store}>
            <App></App>
        </Provider>,
        document.getElementById('app')
    );

    组件中要获得全局store的值,需要用connect函数,connect提供连接React组件与Redux store的作用。

    connect([mapStateToProps], [mapDispatchToProps])

    connect()的第一个参数:mapStateToProps这个函数的第一个参数就是Reduxstore,会自动将store的数据作为props绑定到组件上。

    connect()的第二个参数:mapDispatchToProps它的功能是,将action作为props绑定到组件上

    通俗理解,使用connect可以把statedispatch绑定到React组件,使得组件可以访问到redux的数据。

    常看到下面这种写法:

    export default connect()(App)

    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>
            </div>
        }
    }
    export default connect(
    //这个函数return的对象的值,将自动成为组件的props。
        (state) => {
            return {
                v : state.v
            }
        }
    )(App);

    一旦connect()(App); 连接某个组件,此时这个组件就会:当全局store发送改变时,如同自己的props发生改变一样,从而进行视图更新。

     

    App.js写两个按钮,可以加1,减1

    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()}}>+</button>
                <button onClick={()=>{this.props.minus()}}>-</button>
            </div>
        }
    }
    export default connect(
        (state) => {
            return {
                v : state.v
            }
        },
        (dispatch) => {
            return {
                add(){
                    dispatch({"type" : "ADD"});
                },
                minus(){
                    dispatch({"type" : "MINUS"});
                }
            }
        }
    )(App);

    reducers/index.js提供可预测状态

    export default (state = {"v" : 10} , action) => {
        if(action.type == "ADD"){
            return { "v" : state.v + 1 };
        }else if(action.type == "MINUS"){
            return { "v" : state.v - 1 };
        }
        return state;
    }

     

    学生管理系统小案例:

    reducers/index.js

    const initObj = {
        "arr" : [
            {"id" : 1, "name" : "小明" , "age" : 12},
            {"id" : 2, "name" : "小红" , "age" : 13},
            {"id" : 3, "name" : "小刚" , "age" : 14}  
        ]
    }
    export default (state = initObj, {type, age, name, id})=>{
        if(type == "ADD_STUDENT"){
            return {
                ...state , 
                "arr" : [
                    ...state.arr ,
                    {
                        "id" : state.arr.reduce((a,b)=>{
                            return b.id > a ? b.id : a;
                        },0) + 1,
                        name, 
                        age
                    }
                ]
            }
        }else if(type == "DEL_STUDENT"){
            return {
                ...state,
                "arr" : state.arr.filter(item=>item.id != id)
            }
        }
        return state;
    }

     

    App.js

    import React from "react";
    import {connect} from "react-redux";
    class App extends React.Component {
        constructor() {
            super();
        }
        //增加学生
        add(){
            var name = this.refs.name.value;
            var age = this.refs.age.value;
            this.props.addStudent(name, age)
        }
        render(){
            return <div>
                <p><input type="text" ref="name"/></p>
                <p><input type="text" ref="age"/></p>
                <button onClick={()=>{this.add()}}>增加学生</button>
                {
                    this.props.arr.map((item,index)=>{
                        return <p key={item.id}>
                            {item.id} {item.name} {item.age}<button onClick={()=>{this.props.delStudent(item.id)}}>删除</button>
                        </p>
                    })
                }
            </div>
        }
    }
    export default connect(
    //(state) => {
        //    return { arr : state.arr }
        //},
    //简化写法
        ({arr})=>{
            return { arr }
        },
        (dispatch)=>{
            return {
                addStudent(name, age){
                    dispatch({"type" : "ADD_STUDENT", name, age})
                },
                delStudent(id){
                    dispatch({"type" : "DEL_STUDENT" , id})
                }
            }
        }
    )(App);

    原理解析

    首先connect之所以会成功,是因为Provider组件:

    在原应用组件上包裹一层,使原来整个应用成为Provider的子组件

    接收Reduxstore作为props,通过context对象传递给子孙组件上的connect

     

    connect做了些什么?

    它真正连接 Redux React,它包在我们的容器组件的外一层,它接收Provider提供的 store 里面的state dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。

     

    总结:

    connect()(App),第一个()中接受两个参数,分别是:mapStateToPropsmapDispatchToProps

    这两个参数都是函数,第一个参数函数return的对象的键名将自动和props进行绑定,第二个参数函数return的对象的键名,也将和props进行绑定。

    第一个参数return的对象,是从state中获得值

    第二个参数return的对象,是要改变state的值

     

    如果有兴趣,可以看一下connect函数的API文档:

    https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options

    不管应用程序有多大,store只有一个,它就像天神一样“照耀”所有组件,但默认情况下所有组件是不能得到store的数据的,哪个组件要拿数据,就要connect一下,另外App最大组件确实包裹着所有组件,但不代表App组件连接了就代表其他组件也连接了。


    三、Redux编程-TodoList

    http://www.todolist.cn/

    reducers/index.js中根据项目情况创建reducer

    const initObj = {
        "todos": [
           {"id" : 1, "title" : "吃饭",  "done" : false},
           {"id" : 2, "title" : "睡觉",  "done" : false},
           {"id" : 3, "title" : "打豆豆","done" : false}
        ]
    }
    
    export default (state = initObj, action) => {
        return state;
    }

    分别创建TodoHd.jsTodoBd.jsTodoFt.js三个组件:

    import React from "react";
    export default class TodoHd extends React.Component {
        constructor() {
            super();
        }
    
        render() {
            return <div>
                <h1>我是TodoHd组件</h1>
            </div>
        }
    }
    示例代码

    App.js引入组件

    import React from "react";
    import {connect} from "react-redux";
    
    import TodoHd from "./components/TodoHd.js";
    import TodoBd from "./components/TodoBd.js";
    import TodoFt from "./components/TodoFt.js";
    
    class App extends React.Component {
        constructor() {
            super();
        }
        
        render() {
            return <div>
                <TodoHd></TodoHd>
                <TodoBd></TodoBd>
                <TodoFt></TodoFt>
            </div>
        }
    }
    export default connect()(App)  
    import React from 'react';
    import {connect} from "react-redux";
    import TodoItem from "./TodoItem.js";
    class TodoBd extends React.Component {
        constructor(props){
            super(props);
        }
        render() {
            return (
                <div>
                //{JSON.stringify(this.props.todos)}
                    {
                        this.props.todos.map(item=>{
                        //return <div key={item.id}>{item.title}</div>
                            return <TodoItem key={item.id} item={item}></TodoItem>
                        })
                    }
                </div>
            );
        }
    }  
    //connect目的是问“天”要数据,要通天。
    //“通天”是比喻,就是说要问store要数据
    export default connect(
        (state)=>{
            return {
                todos : state.todos
            }
        }
    )(TodoBd);

    TodoItem.js

    import React from 'react';
    export default class TodoItem extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div className="todoItem">
                    <input type="checkbox" checked={this.props.item.done}/>
                    <span>{this.props.item.title}</span>
                    <button>删除</button>
                </div>
            );
        }
    }

    TodoHd.js增加待办事项

    import React from 'react';
    import {connect} from "react-redux";
    class TodoHd extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div>
                    <input type="text" ref="titleTxt"/>
                    <button onClick={()=>{
                        this.props.addTodo(this.refs.titleTxt.value);
                        this.refs.titleTxt.value = "";
                    }}>添加</button>
                </div>
            );
        }
    }
    //这个组件要通天的目的不是要数据,而是改变数据
    export default connect(
        null ,
        (dispatch)=>{
            return {
                addTodo(title){
                    dispatch({"type":"ADDTODO", title})
                }
            }
        }
    )(TodoHd);

    reducers/index.js写可预测状态  

    const initObj = {
        "todos" : [
            ...
        ],
    }
    export default (state = initObj, action)=>{
        if(action.type == "ADDTODO"){
            return {
                ...state, 
                "todos" : [
                    ...state.todos, 
                    {
                        "id" : state.todos.reduce((a,b)=>{
                            return b.id > a ? b.id : a;
                        },0) + 1,
                        "title" : action.title, 
                        "done" : false
                    }
                ]
            }
        }
        return state;
    }

    TodoFt.js

    import React from "react";
    import {connect} from "react-redux";
    class TodoFt extends React.Component {
        constructor() {
            super();
        }
    
        render() {
            return <div>
                当前:共{this.props.todos.length}条信息--
                已做{this.props.todos.filter(item => item.done).length}条--
                未做{this.props.todos.filter(item => !item.done).length}条
            </div>
        }
    }
    
    export default connect(
        (state)=>{
            return {
                todos:state.todos
            }
        }
    )(TodoFt)
    示例代码

    因为TodoItem这个组件是不通天的,所以TodoItem是不能自己独立dispatchstore的。

    此时就需要TodoBd帮助,因为TodoBd是通天。

    这是套路:所有被循环语句map出来的组件,一律不通天,数据父亲给,改变store的能力父亲给。

    TodoBd.js组件引入了TodoItem.js组件,因为TodoItem组件是被map出来的,所以信息要传给每一个TodoItem,而不是让TodoItem自己通天拿数据。

    TodoBd.js

    import React from 'react';
    import {connect} from "react-redux";
    import TodoItem from "./TodoItem.js";
    
    class TodoBd extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            //根据全局的show属性来决定当前todos数组
            if(this.props.show == "ALL"){
                var todos = this.props.todos;
            }else if(this.props.show == "ONLYDONE"){
                var todos = this.props.todos.filter(item=>item.done);
            }else if(this.props.show == "ONLYUNDONE"){
                var todos = this.props.todos.filter(item=>!item.done);
            }
    
            return (
                <div>
                    {
                        todos.map(item=>{
                            return <TodoItem
                                key={item.id}
                                item={item}
                                delTodo={this.props.delTodo.bind(this)}
                                changeTodo={this.props.changeTodo.bind(this)}
                            ></TodoItem>
                        })
                    }
                </div>
            );
        }
    }
    export default connect(
        (state)=>{
            return {
                todos : state.todos ,
                show : state.show
            }
        },
        (dispatch)=>{
            return {
                delTodo(id){
                    dispatch({"type" : "DELTODO", id});
                },
                changeTodo(id , k , v){
                    dispatch({"type" : "CHANGETODO", id, k, v});
                }
            }
        }
    )(TodoBd);

    TodoItem.js

    import React from 'react';
    export default class TodoItem extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                "onEdit" : false
            }
        }
        render() {
         const {id, title, done} = this.props.item;
            return (
                <div>
                    <input 
                        type="checkbox" checked={done}
                        onChange={(e)=>{
                  this.props.changeTodo(id, "done", e.target.checked)
                }}
                    />
                    {
                        this.state.onEdit 
                        ?
                        <input 
                            type="text" 
                            defaultValue={title} 
                            onBlur={(e)=>{
                                this.props.changeTodo(id,"title", e.target.value)
                                this.setState({"onEdit" : false})
                            }}
                        />
                        :
                        <span onDoubleClick={()=>{this.setState({"onEdit":true})}}>
                {title}
              </span>
                    }
    
                    <button onClick={()=>{this.props.delTodo(id)}}>删除</button>
                </div>
            );
        }
    }

    index.js

    const initObj = {
        "todos" : [
            ...
        ],
        "show" : "ALL"       //ALL、ONLYDONE、ONLYUNDONE
    }
    export default (state = initObj, action) => {
        if(action.type == "ADDTODO"){
            ...
        }else if(action.type == "DELTODO"){
            return {
                ...state, 
                "todos" : state.todos.filter(item => item.id != action.id)
            }
        }else if(action.type == "CHANGETODO"){
            return {
                ...state, 
                "todos" : state.todos.map(item => {
                    //如果遍历到的item项和传入的aciton的id项不一样,此时返回原item
                    if(item.id != action.id) return item;
                    //否则返回修改之后的item
                    return {
                        ...item ,
                        [action.k] : action.v
                    }
                })
            }
        }else if(action.type == "CHANGESHOW"){
            return {
                ...state, 
                "show" : action.show
            }
        }
        return state;
    }

    TodoFt.js

    import React from 'react';
    import {connect} from "react-redux";
    import classnames from "classnames";
    
    class TodoFt extends React.Component {
        constructor(props) {
            super(props);
        }
    
        render() {
            return (
                <div>
                    <p>
                        当前共{this.props.todos.length}条信息
    做完{this.props.todos.filter(item=>item.done).length}条
    未做{this.props.todos.filter(item=>!item.done).length}条
                    </p>
                    <p>
                        <button className={classnames({"cur":this.props.show == 'ALL'})}                                 
                onClick={()=>{this.props.changeShow('ALL')}}>查看全部             </button> <button className={classnames({"cur":this.props.show == 'ONLYDONE'})} onClick={()=>{this.props.changeShow('ONLYDONE')}}>仅看已做 </button> <button className={classnames({"cur":this.props.show == 'ONLYUNDONE'})} onClick={()=>{this.props.changeShow('ONLYUNDONE')}}>仅看未做 </button> </p> </div> ); } } export default connect( (state) => { return { todos : state.todos , show : state.show } }, (dispatch) => { return { changeShow(show){ dispatch({"type" : "CHANGESHOW" , show}) } } } )(TodoFt);

    四、logger插件

    redux-logger用来辅助开发。

    npm install --save redux-logger

    改变main.js:

    import React from "react";
    import ReactDOM from "react-dom";
    import {createStore , applyMiddleware} from "redux";
    import {Provider} from "react-redux";
    import logger from "redux-logger";
    
    import App from "./App.js";
    //引入reducer
    import reducer from "./reducers/index.js";
    
    //创建store
    const store = createStore(reducer , applyMiddleware(logger));
    
    ReactDOM.render(
        <Provider store={store}>
            <App></App>
        </Provider>
        ,
        document.getElementById("app-container")
    );

    也可以使用redux-devtools这个插件。

    npm install --save-dev redux-devtools
    npm install --save-dev redux-devtools-log-monitor
    npm install --save-dev redux-devtools-dock-monitor
    npm install --save-dev redux-devtools-chart-monitor

    文档:

    https://github.com/reduxjs/redux-devtools

    https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md


    五、combineReducersbindActionCreators

    一个网页的应用程序可能是多个reducer,合并为一个reducer,比如countertodoreducer

    Redux提供的combineReducers方法,用于reducer的拆分,只要定义各个子reducer函数,然后用这个方法,将它们合成一个大的reducer

    Redux提供的bindActionCreators方法,用于通过dispatchaction包裹起来,这条可以通过bindActionCreators创建的方法,直接调用dispatch(action)“隐式调用”。


    5.1 combineReducers

    reducers/counter.js就是一个普通的纯函数:

    export default (state = {"v" : 10},action)=>{
        return state;
    }

    reducers/todo.js提供的数据:

    const initObj = {
        "todos": [
            { "id": 1, "title": "吃饭", "done": false },
            { "id": 2, "title": "睡觉", "done": false },
            { "id": 3, "title": "打豆豆", "done": true }
        ] 
    };
    
    export default (state = initObj, action) => {
        return state
    }
    示例代码

    reducers/index.js要用redux提供的combineReducers来进行智能合并

    import { combineReducers } from "redux";
    import counter from "./counter.js";
    import todos from "./todos.js";
    
    //暴露合并的reducer
    export default combineReducers({
        counter,
        todos
    })

    main.js

    import React from "react";
    import ReactDOM from "react-dom";
    import {createStore} from "redux";
    import {Provider} from "react-redux";
    
    import App from "./containers/App.js";
    //引入reducer
    import reducer from "./reducers";
    
    // 创建 Redux store 来存放应用的状态。
    const store = createStore(reducer);
    
    ReactDOM.render(
        <Provider store={store}>
            <App></App>
        </Provider>,
        document.getElementById("app")
    );

    containers/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>
                </div>
            );
        }
    }
    export default connect(
        ({counter}) => ({
            v : counter.v
        })
    )(App);

    components/TodoList/index.js组件

    import React from "react";
    import { connect } from "react-redux";
    class TodoList extends React.Component {
        constructor(){
            super();
        }
        render() {
            return (
                <div>
                    <h1>我是TodoList</h1>
                    {
                        this.props.todos.map(item=>{
                            return <p key={item.id}>{item.title}</p>
                        })
                    }
                </div>
            )
        }
    };
    export default connect(
       ({todos: {todos}})=>{
            return {
                todos
            }
       }
    )(TodoList)

    containers/App.js引入组件:

    import React from 'react';
    import TodoList from "../components/todolist/index.js";
    import Counter from "../components/counter/index.js";
     
    export default class App extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div>
                    <Counter></Counter>
                    <TodoList></TodoList>
                </div>
            );
        }
    }

    5.2 bindActionCreators

    bindActionCreators会将actiondispatch绑定并返回一个对象,这个对象会作为props的一部分传入组件中。

    bindActionCreators主要作用:一般情况下,可以通过Providerstore通过Reactconnext属性向下传递,bindActionCreators的唯一用处就是需要传递action creater到子组件,并且该子组件并没有接收到父组件上传递的storedispatch

    官方的文件夹结构:https://github.com/reduxjs/redux/tree/master/examples/todomvc/src

    actions/counterActions.js新建actions文件夹存放type

    // 我们把return一个action的函数叫做“action creator”
    // 所以这个文件向外暴露了几个动作
    export const add = () => ({ type: "ADD" })
    export const minus = () => ({ type: "MINUS" })
    export const cheng = () => ({ type: "CHENG" })
    export const chu = () => ({ type: "CHU" })

    counter/index.js计数器组件

    import React from 'react';
    import {bindActionCreators} from "redux";
    import {connect} from "react-redux";
    import * as counterActions from "../../actions/counterActions.js";
    
    class Counter extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div>
                    <h1>Counter : {this.props.v}</h1>
                    <button onClick={()=>{this.props.counterActions.add()}}>加</button>
                    <button onClick={()=>{this.props.counterActions.minus()}}>减</button>
                    <button onClick={()=>{this.props.counterActions.cheng()}}>乘</button>
                    <button onClick={()=>{this.props.counterActions.chu()}}>除</button>
                </div>
            );
        }
    }
    export default connect(
        ({counter}) => ({
            v : counter.v
        }),
        (dispatch) => ({
        //这里的dispatch,等同于store中的store.dispatch,用于组合action
            counterActions : bindActionCreators(counterActions , dispatch)
        })
    )(Counter);

    app/reducers/counter.js

    import {ADD, MINUS, CHENG, CHU} from "../constants/COUNTER.js";
    
    export default (state = {"v" : 0} , action) => {
        if(action.type == "ADD"){
            return {
                ...state , 
                "v" : state.v + 1
            }
        }else if(action.type == "MINUS"){
            return {
                ...state , 
                "v" : state.v - 1
            }
        }else if(action.type == "CHENG"){
            return {
                ...state , 
                "v" : state.v * 2
            }
        }else if(action.type == "CHU"){
            return {
                ...state , 
                "v" : state.v / 2
            }
        }
        return state;
    }
    示例代码

    todolist/index.js

    import React from 'react';
    import TodoHd from "./TodoHd.js";
    import TodoBd from "./TodoBd.js";
    
    export default class TodoList extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                  <div>
                      <h1>TodoList</h1>
                      <TodoHd></TodoHd>
                      <TodoBd></TodoBd>
                  </div>
            );
        }
    }

    TodoHd.js

    import React from 'react';
    import {bindActionCreators} from "redux";
    import {connect} from "react-redux";
    import * as todoActions from "../../actions/todoActions.js";
    
    class TodoHd extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div>
                    <input type="text" ref="titleTxt"/>
                    <button onClick={()=>{this.props.todoActions.add(this.refs.titleTxt.value)}}
                >添加
              </button>
                </div>
            );
        }
    }
    export default connect(
        null ,
        (dispatch) => ({
            todoActions : bindActionCreators(todoActions , dispatch)
        })
    )(TodoHd);

    TodoBd.js

    import React from 'react';
    import {bindActionCreators} from "redux";
    import {connect} from "react-redux";
    import * as todoActions from "../../actions/todoActions.js";
    
    class TodoBd extends React.Component {
        constructor(props) {
            super(props);
        }
        render() {
            return (
                <div>
                    {
                        this.props.todos.map(item=>{
                            return <p key={item.id}>
                                {item.title}
                                <button onClick={()=>{this.props.todoActions.del(item.id)}}>
                     删除
                      </button>
                            </p>
                        })
                    }
                </div>
            );
        }
    }
    
    export default connect(
        ({todo}) => ({
            todos : todo.todos
        }) ,
        (dispatch) => ({
            todoActions : bindActionCreators(todoActions , dispatch)
        })
    )(TodoBd);

    为了防止actiontype命名冲突,此时要单独存放在const文件夹中:

    appconstantsCOUNTER.js

    export const ADD = "ADD_COUNTER";
    export const MINUS = "MINUS_COUNTER";
    export const CHENG = "CHENG_COUNTER";
    export const CHU = "CHU_COUNTER";

    appconstantsTODO.js

    export const ADD = "ADD_TODO";
    export const DEL = "DEL_TODO";

    然后就可以在以下文件中,引入以上常量,然后使用大写的常量替换type字符串

    l actions中的counterActions.jstodoActions.js

    l reducers中的todo.jscounter.js

    actions/TodoActions.js

    import {ADD , DEL} from "../constants/TODO.js";
    export const add = (title) => ({"type" : ADD, title});
    export const del = (id) => ({"type" : DEL, id});

    actions/counterActions.js

    import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
    export const add = () => ({"type" : ADD});
    export const minus = () => ({"type" : MINUS});
    export const cheng = () => ({"type" : CHENG});
    export const chu = (n) => ({"type" : CHU , n});

    reducers/todo.js

    import {ADD , DEL} from "../constants/TODO.js";
    const initObj = {
        "todos" : [
            {"id" : 1 , "title" : "吃饭" , "done" : false},
            {"id" : 2 , "title" : "睡觉" , "done" : false},
            {"id" : 3 , "title" : "打豆豆" , "done" : false}
        ]
    }
    export default (state = initObj , action) => {
        if(action.type == ADD){
            return {
                ...state ,
                "todos" : [
                    ...state.todos ,
                    {
                        "id" : state.todos.reduce((a,b)=>{return b.id > a ? b.id : a},0) + 1,
                        "title": action.title,
                        "done" : action.done
                    }
                ]
            }
        }else if(action.type == DEL){
            return {
                ...state ,
                "todos" : state.todos.filter(item => item.id != action.id)
            }
        }
        return state;
    }

    reducers/counter.js

    import {ADD , MINUS , CHENG , CHU} from "../constants/COUNTER.js";
    
    export default (state = {"v" : 0} , action) => {
        if(action.type == ADD){
            return {
                ...state , 
                "v" : state.v + 1
            }
        }else if(action.type == MINUS){
            return {
                ...state , 
                "v" : state.v - 1
            }
        }else if(action.type == CHENG){
            return {
                ...state , 
                "v" : state.v * 2
            }
        }else if(action.type == CHU){
            return {
                ...state , 
                "v" : state.v / action.n
            }
        }
        return state;
    }

  • 相关阅读:
    小程序开发之初体验
    phantomjs 爬去动态页面
    css实现三角形
    多种方式实现千位分隔符
    基于浏览器的人脸识别标记
    Axios源码阅读笔记#1 默认配置项
    基于图形检测API(shape detection API)的人脸检测
    页面性能优化
    目标
    HelloWorld!
  • 原文地址:https://www.cnblogs.com/rope/p/10741396.html
Copyright © 2011-2022 走看看