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;
    }

  • 相关阅读:
    How to build Linux system from kernel to UI layer
    Writing USB driver for Android
    Xposed Framework for Android 8.x Oreo is released (in beta)
    Linux Smartphone Operating Systems You Can Install Today
    Librem 5 Leads New Wave of Open Source Mobile Linux Contenders
    GUADEC: porting GNOME to Android
    Librem 5 – A Security and Privacy Focused Phone
    GNOME and KDE Join Librem 5 Linux Smartphone Party
    Purism计划推出安全开源的Linux Librem 5智能手机
    国产系统之殇:你知道的这些系统都是国外的
  • 原文地址:https://www.cnblogs.com/rope/p/10741396.html
Copyright © 2011-2022 走看看