• React初识整理(五)--Redux和Flux(解决状态传递问题)


    Flux

    1、引入:在React的应⽤中,状态管理是⼀个⾮常重要的⼯作。我们不会直接对DOM节点进⾏操作,⽽是通过将数据设置给state,由state来同步UI,这种⽅式有个潜在的问题,每个组件都有独⽴的state,并且不能相互传递。如果从⼀个组件将数据传递给另⼀个组件,需要通过props。⽽props的特点是⾃顶⽽下的传递,那么⼦组件要传递给⽗组件就会⽐较麻烦。当这种需求越来越多后,状态管理就会变得更加困难。

    2、定义:它不是⼀种⼯具或框架,⽽是⼀种架构模式。它把所有的数据都集中放在了⼀个叫store的对象中。以后每⼀个组件都不在直接操作state,⽽是把更改数据的命令封装成action,然后dispatch给store。 store再完成state的更改,对应的组件 完成UI更新。

    3、核心概念:

       - store:相当于数据中⼼,可以有多个

       - action:操作命令

       - dispatch:分发action的对象

       - view:视图组件

      - 过程:view -> action -> dispatch -> store -> view 

    Redux

    1、Flux的实现,也扩展了自身。它可以用于任何组件化开发中,比较常见用于React中。

      ①核心概念:

         - store:数据中⼼,只有⼀个

         - action:操作命令 - action creator:创建命令的⽅法

         - dispatch:分发action的对象 - middleware:中间件

         - reducer:更新state数据的⽅法

         - view:视图

       ②流程:view -> action creator -> action -> dispatch -> middleware -> reducer -> -> state -> view

       ③reducer必须是1个纯函数,就是函数里的数据没有副作用的,每调用1次函数的结果必须是一致的,且只能操作state,比如函数里做加法,那么就只能做加法,而不能一会儿加法一会儿减法。如果函数里有异步操作,就会改变函数的操作方法,这样就不会纯函数。

    2、具体用法: 安装: npm i redux npm i react-redux

      ①编写action creator。 在src下创建actions文件夹,actions下有index.js,用于储存所有的action操作命令。

    /src/actions/index.js
     export const setVisible = (visible) => {
        return {
            // type必须写,是命令的名称,必须全大写,中间用_连接
            type: 'ADD_TODO',  
            visible
        }
    }

      ②编写reducer:在src文件夹下创建reducers文件夹,然后在此文件下建立index.js,用于将所有的更新state数据的方法reducer都合并到一起,然后创建1个componentReducer.js文件存放单一的1个reducer。 将传送过来的值设置到store的state里:

    // /src/reducers/componentReducer.js
    // 传入第一个参数state,刚开始可能什么都没有,所以给个默认初始值
    // 传入第二个参数action,即命令,它包含了传过来的type和state的值。1个命令只改1个属性
    
    export default (state = { visible: false }, action) => {
        switch (action.type) {
                
            // 我们不直接修改state的值,而是返回1个新的对象,这样在做优化时即便浅比较也会更新。
            // 浅比较:赋对象的变量储存的是地址,当我们改属性的值时,变量储存的地址没变,这样浅比较时就认为它是没变化的,从而在优化时不会更新后续组件
            // 返回用ES6的扩展运算符,对象里只能有唯一键,相同的会替换。
            
            case "SET_VISIBLE":
                return {...state,visible:action.visible}
            default:
                return state;
        }
    }

      将多个reducer合并为一个:

    // /src/reducers/index.js
    // 将所有的更新state数据的方法reducer都合并到一起
    import { combineReducers } from 'redux';
    import componentReducer from './componentReducer'
    import updateStudentReducer from './updateStudentReducer'
    export default combineReducers({
        componentReducer,
        updateStudentReducer
    })

      ③将reducer组合到store中:用到react-redux的Provider组件,将其设置成根组件,然后将store设置给它。

    // /src/store/index.js
    // store主要把reducer结合进来
    
    import React from 'react';
    import reducers from '../reducers';
    import { Provider } from 'react-redux'
    // 创建store
    import { createStore} from 'redux'
    let store = createStore(reducers)
    
    // ⽤到react-redux的Provider组件,将其设置成根组件,然后将store设置给它
    export default (props) => {
        return <Provider store={store}>
            {/* 将入口的组件传进来*/}
            {props.children}
        </Provider>
    }

    我们在src下建立了index.js,作为项目的主入口,然后用< Store>将入口装起来,作为所有子组件的根级元素。

    // /src/index.js
    import React from "react"
    import ReactDOM from "react-dom"
    import Store from "./antd/store"
    import Router from "./router"
    // 把store作为所有组件的根组件
    ReactDOM.render(<Store><Router /></Store>, document.getElementById("root"));

    到这里,我们就把基础配置完成,下面就是如何使用它了。

      ④将组件和redux关联起来:我们先把子组件和redux关联起来,也就是将子组件和store建立联系。在这里我们使用react-redux下的connect方法

    import { connect } from "react-redux"
    class StudentsList extends Component {
        //省略
    }
    // 关联当前组件StudentsList到store后默认导出。关联之后props里才有dispatch方法
    export default connect()(StudentsList)

    connect()()方法后置两个圆括号, - 第一个圆括号用于映射,圆括号里可以是一个回调函数,自带1个参数store,它存储着所有的state状态,我们可以直接调用。映射成功了的子组件中,就可以直接通过 props来获取state里的值了。如:this.props.visible就可以获得我存储在store里的visible的值 - 第二个圆括号用于关联,圆括号里写1个要关联的组件名。即将子组件关联到store上。关联之后子组件中的props才能dispatch方法,用于操作命令的分发。如:

    import { setVisible } from "../actions"
      // 使用dispatch分发action对象到setVisible,并设置值为true,这样就调用了store里面的actions操作命令里的setVisible命令
    this.props.dispatch(setVisible(true))

    这样我们就可以通过dispatch来分发action命令,从而调用之前我们写的命令setVisible来改变对应state的状态了。

      ⑤异步操作存在问题及解决

      在⼀般情况下,都是发出action后,由reducer完成state的计算,然后更新组件。但是如果遇到 有异步操作怎么办呢?reducer是纯函数,不适合做除设置state以外的其他操作。 action creator的⽅法要求返回的是⼀个命令对象,异步操作在这⾥也有问题。 我们来看1个例子:

    // /src/actions/index.js
    // 当要发送请求时,因为axios是个异步操作,它会先return,那么返回的student里就没有值,所有在这里要引入中间件thunk
    import axios from "axios";
    export const setStudent = (id) => {
        let student;
        axios({
            method:"get",
            url:"/students/"+id
        }).then(({data})=>{
            student = data
        })
        return {
            type: 'SET_VISIBLE',  
            student
        }
    }

      我用axios发送ajax向服务器请求数据,由于ajax是异步操作,那么return会比student = data(将取回的数据赋值给student)先执行,结果我们就return的student结果只能为undefined。return放到then方法里面也不行,如果放里面就无法说清return是针对哪个地方的了。那么怎么解决呢?

       redux提供了中间件来解决这个问题。我们引入中间件:redux-thunk, 首先我们将前面的store下的index.js进行修改:

    // store主要把reducer结合进来
    
    import React from 'react';
    import reducers from '../reducers';
    import { Provider } from 'react-redux'
    
    // 引入异步操作的中间插件redux-thunk。安装:npm i redux-thunk
    import thunk from 'redux-thunk'
    // 创建store,然后用 applyMiddleware来应用中间插件
    import { createStore, applyMiddleware } from 'redux'
    let store = createStore(reducers, applyMiddleware(logger,thunk))
    
    // ⽤到react-redux的Provider组件,将其设置成根组件,然后将store设置给它
    export default (props) => {
        return <Provider store={store}>
            {/* 将入口的传进来 */}
            {props.children}
        </Provider>
    }

    在这里,引入了中间件thunk,并将这个中间件应用到了reducers里面。 然后我们调用axios来发送异步请求:

    // /src/actions/index.js
    import axios from "axios";
    export const setStudent = (id) => {
        return (dispatch, getState) => {
            axios({
                method: "get",
                url:"/students/"+id
            }).then(({ data }) => {
            //应用中间件后,这里可以使用dispatch来发送action命令更新数据
                dispatch({
                    type: 'SET_STUDENT',
                    updateStudent: data
                });
            })
        }
    }

    得到数据后,我们在需要用到的组件里进行映射:

    // /src/students/updateStudents.js
    export default connect(
        // 简化代码 ,箭头函数中的圆括号()就是return的意思
        // ({ componentReducer: { visible } }) => ( visible )
        // 等同于:
        (store) => {
            return {
                visible: store.componentReducer.visible,
                updateStudent:store.updateStudentReducer.updateStudent
            }}
    )(
        Form.create({
            // antd里的解决受控组件的方法
            //在这里我们就用上面映射回来的值设置到对应的输入框了。
            mapPropsToFields(props) {
                return {
                    name: Form.createFormField({
                        value: props.updateStudent.name
                    }),
                    age: Form.createFormField({
                        value: props.updateStudent.age
                    }),
                    gender: Form.createFormField({
                        value: props.updateStudent.gender
                    }),
                };
            }
        })(UpdateStudents)
    )

      ⑥在这里补充1个使用的中间件logger

    logger是一个日志处理的中间件,它会自动打印操作时间的前后值的变化。

    其中 prev state的内容为操作前的内容, next state为操作后的内容,这样方便对比。

    下载: npm i redux-logger.

    使用:

    import    {createLogger}    from 'redux-logger';
    const    logger    =    createLogger();
    let    store    =    createStore(reducers,applyMiddleware(logger))

     

  • 相关阅读:
    Mysql系列【解决mysql连接数太多】
    并发编程系列【线程池七大核心参数】
    C信号处理的基础
    设计模式之Command
    Ext文件系统
    内存管理
    设计模式之Decorator Pattern
    设计模式之singleton
    Quicklz压缩算法
    设计模式之Factory
  • 原文地址:https://www.cnblogs.com/zhangzhiyong/p/10146265.html
Copyright © 2020-2023  润新知