• React之Redux详解


    一、Redux

    1、是什么

    React是用于构建用户界面的,帮助我们解决渲染DOM的过程

    而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享

    如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程

    这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的

    redux就是一个实现上述集中管理的容器,遵循三大基本原则:

    • 单一数据源
    • state 是只读的
    • 使用纯函数来执行修改

    注意的是,redux并不是只应用在react中,还与其他界面库一起使用,如Vue

    2、工作原理

    redux要求我们把数据都放在 store公共存储空间

    一个组件改变了 store 里的数据内容,其他组件就能感知到 store的变化,再来取数据,从而间接的实现了这些数据传递的功能

    工作流程图如下所示:

    根据流程图,可以想象,React Components 是借书的用户, Action Creactor 是借书时说的话(借什么书), Store 是图书馆管理员,Reducer 是记录本(借什么书,还什么书,在哪儿,需要查一下), state 是书籍信息

    整个流程就是借书的用户需要先存在,然后需要借书,需要一句话来描述借什么书,图书馆管理员听到后需要查一下记录本,了解图书的位置,最后图书馆管理员会把这本书给到这个借书人

    转换为代码是,React Components 需要获取一些数据, 然后它就告知 Store 需要获取数据,这就是就是 Action Creactor , Store 接收到之后去 Reducer 查一下, Reducer 会告诉 Store 应该给这个组件什么数据

    3、如何使用

    创建一个store的公共数据区域

    1. import { createStore } from 'redux' // 引入一个第三方的方法
    2. const store = createStore() // 创建数据的公共存储区域(管理员)

    还需要创建一个记录本去辅助管理数据,也就是reduecer,本质就是一个函数,接收两个参数stateaction,返回state

    1. // 设置默认值
    2. const initialState = {
    3. counter: 0
    4. }
    5. const reducer = (state = initialState, action) => {
    6. }

    然后就可以将记录本传递给store,两者建立连接。如下:

    const store = createStore(reducer)
    

    如果想要获取store里面的数据,则通过store.getState()来获取当前state

    console.log(store.getState());
    

    下面再看看如何更改store里面数据,是通过dispatch来派发action,通常action中都会有type属性,也可以携带其他的数据

    1. store.dispatch({
    2. type: "INCREMENT"
    3. })
    4. store.dispath({
    5. type: "DECREMENT"
    6. })
    7. store.dispatch({
    8. type: "ADD_NUMBER",
    9. number: 5
    10. })

    下面再来看看修改reducer中的处理逻辑:

    1. const reducer = (state = initialState, action) => {
    2. switch (action.type) {
    3. case "INCREMENT":
    4. return {...state, counter: state.counter + 1};
    5. case "DECREMENT":
    6. return {...state, counter: state.counter - 1};
    7. case "ADD_NUMBER":
    8. return {...state, counter: state.counter + action.number}
    9. default:
    10. return state;
    11. }
    12. }

    注意,reducer是一个纯函数,不需要直接修改state

    这样派发action之后,既可以通过store.subscribe监听store的变化,如下:

    1. store.subscribe(() => {
    2. console.log(store.getState());
    3. })

    React项目中,会搭配react-redux进行使用

    完整代码如下:

    1. const redux = require('redux');
    2. const initialState = {
    3. counter: 0
    4. }
    5. // 创建reducer
    6. const reducer = (state = initialState, action) => {
    7. switch (action.type) {
    8. case "INCREMENT":
    9. return {...state, counter: state.counter + 1};
    10. case "DECREMENT":
    11. return {...state, counter: state.counter - 1};
    12. case "ADD_NUMBER":
    13. return {...state, counter: state.counter + action.number}
    14. default:
    15. return state;
    16. }
    17. }
    18. // 根据reducer创建store
    19. const store = redux.createStore(reducer);
    20. store.subscribe(() => {
    21. console.log(store.getState());
    22. })
    23. // 修改store中的state
    24. store.dispatch({
    25. type: "INCREMENT"
    26. })
    27. // console.log(store.getState());
    28. store.dispatch({
    29. type: "DECREMENT"
    30. })
    31. // console.log(store.getState());
    32. store.dispatch({
    33. type: "ADD_NUMBER",
    34. number: 5
    35. })
    36. // console.log(store.getState());

    小结

    • createStore可以帮助创建 store
    • store.dispatch 帮助派发 action , action 会传递给 store
    • store.getState 这个方法可以帮助获取 store 里边所有的数据内容
    • store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行

    二、Redux中间件

    1、是什么

    中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的

    在上篇文章中,了解到了Redux整个工作流程,当action发出之后,reducer立即算出state,整个过程是一个同步的操作

    那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件

    Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理,如下图:

    其本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能

    2、常用的中间件

    有很多优秀的redux中间件,如:

    • redux-thunk:用于异步操作
    • redux-logger:用于日志记录

    上述的中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,依次执行

    然后作为第二个参数传入到createStore

    1. const store = createStore(
    2. reducer,
    3. applyMiddleware(thunk, logger)
    4. );
    redux-thunk

    redux-thunk是官网推荐的异步处理中间件

    默认情况下的dispatch(action)action需要是一个JavaScript的对象

    redux-thunk中间件会判断你当前传进来的数据类型,如果是一个函数,将会给函数传入参数值(dispatch,getState)

    • dispatch函数用于我们之后再次派发action
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

    所以dispatch可以写成下述函数的形式:

    1. const getHomeMultidataAction = () => {
    2. return (dispatch) => {
    3. axios.get("http://xxx.xx.xx.xx/test").then(res => {
    4. const data = res.data.data;
    5. dispatch(changeBannersAction(data.banner.list));
    6. dispatch(changeRecommendsAction(data.recommend.list));
    7. })
    8. }
    9. }
    redux-logger

    如果想要实现一个日志功能,则可以使用现成的redux-logger

    1. import { applyMiddleware, createStore } from 'redux';
    2. import createLogger from 'redux-logger';
    3. const logger = createLogger();
    4. const store = createStore(
    5. reducer,
    6. applyMiddleware(logger)
    7. );

    这样我们就能简单通过中间件函数实现日志记录的信息

    3、实现原理

    首先看看applyMiddlewares的源码

    1. export default function applyMiddleware(...middlewares) {
    2. return (createStore) => (reducer, preloadedState, enhancer) => {
    3. var store = createStore(reducer, preloadedState, enhancer);
    4. var dispatch = store.dispatch;
    5. var chain = [];
    6. var middlewareAPI = {
    7. getState: store.getState,
    8. dispatch: (action) => dispatch(action)
    9. };
    10. chain = middlewares.map(middleware => middleware(middlewareAPI));
    11. dispatch = compose(...chain)(store.dispatch);
    12. return {...store, dispatch}
    13. }
    14. }

    所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getStatedispatch这两个方法

    在上面的学习中,我们了解到了redux-thunk的基本使用

    内部会将dispatch进行一个判断,然后执行对应操作,原理如下:

    1. function patchThunk(store) {
    2. let next = store.dispatch;
    3. function dispatchAndThunk(action) {
    4. if (typeof action === "function") {
    5. action(store.dispatch, store.getState);
    6. } else {
    7. next(action);
    8. }
    9. }
    10. store.dispatch = dispatchAndThunk;
    11. }

    实现一个日志输出的原理也非常简单,如下:

    1. let next = store.dispatch;
    2. function dispatchAndLog(action) {
    3. console.log("dispatching:", addAction(10));
    4. next(addAction(5));
    5. console.log("新的state:", store.getState());
    6. }
    7. store.dispatch = dispatchAndLog;

    三、项目中使用Redux 

    1、背景

    在上面文章了解中,我们了解到redux是用于数据状态管理,而react是一个视图层面的库

    如果将两者连接在一起,可以使用官方推荐react-redux库,其具有高效且灵活的特性

    react-redux将组件分成:

    • 容器组件:存在逻辑处理
    • UI 组件:只负责现显示和交互,内部不处理逻辑,状态由外部控制

    通过redux将整个应用状态存储到store中,组件可以派发dispatch行为actionstore

    其他组件通过订阅store中的状态state来更新自身的视图

    2、如何做

    使用react-redux分成了两大核心:

    • Provider
    • connection
    Provider

    redux中存在一个store用于存储state,如果将这个store存放在顶层元素中,其他组件都被包裹在顶层元素之上

    那么所有的组件都能够受到redux的控制,都能够获取到redux中的数据

    使用方式如下:

    1. <Provider store = {store}>
    2. <App />
    3. <Provider>
    connection

    connect方法将store上的getState和 dispatch包装成组件的props

    导入conect如下:

    import { connect } from "react-redux";
    

    用法如下:

    connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    

    可以传递两个参数:

    • mapStateToProps

    • mapDispatchToProps

    mapStateToProps

    redux中的数据映射到react中的props中去

    如下:

    1. const mapStateToProps = (state) => {
    2. return {
    3. // prop : state.xxx | 意思是将state中的某个数据映射到props中
    4. foo: state.bar
    5. }
    6. }

    组件内部就能够通过props获取到store中的数据

    1. class Foo extends Component {
    2. constructor(props){
    3. super(props);
    4. }
    5. render(){
    6. return(
    7. // 这样子渲染的其实就是state.bar的数据了
    8. this.props.foo
    9. )
    10. }
    11. }
    12. Foo = connect()(Foo)
    13. export default Foo
    mapDispatchToProps

    redux中的dispatch映射到组件内部的props

    1. const mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch
    2. return {
    3. onClick: () => {
    4. dispatch({
    5. type: 'increatment'
    6. });
    7. }
    8. };
    9. }
    1. class Foo extends Component {
    2. constructor(props){
    3. super(props);
    4. }
    5. render(){
    6. return(
    7. )
    8. }
    9. }
    10. Foo = connect()(Foo);
    11. export default Foo;
    小结

    整体流程图大致如下所示:

    3、项目结构

    可以根据项目具体情况进行选择,以下列出两种常见的组织结构

    按角色组织(MVC)

    角色如下:

    • reducers
    • actions
    • components
    • containers

    参考如下:

    1. reducers/
    2. todoReducer.js
    3. filterReducer.js
    4. actions/
    5. todoAction.js
    6. filterActions.js
    7. components/
    8. todoList.js
    9. todoItem.js
    10. filter.js
    11. containers/
    12. todoListContainer.js
    13. todoItemContainer.js
    14. filterContainer.js
    按功能组织

    使用redux使用功能组织项目,也就是把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码

    Redux中,不同的角色就是reduceractions和视图,而应用功能对应的就是用户界面的交互模块

    参考如下:

    1. todoList/
    2. actions.js
    3. actionTypes.js
    4. index.js
    5. reducer.js
    6. views/
    7. components.js
    8. containers.js
    9. filter/
    10. actions.js
    11. actionTypes.js
    12. index.js
    13. reducer.js
    14. views/
    15. components.js
    16. container.js

    每个功能模块对应一个目录,每个目录下包含同样的角色文件:

    • actionTypes.js 定义action类型
    • actions.js 定义action构造函数
    • reducer.js 定义这个功能模块如果响应actions.js定义的动作
    • views 包含功能模块中所有的React组件,包括展示组件和容器组件
    • index.js 把所有的角色导入,统一导出

    其中index模块用于导出对外的接口

    导入方法如下:

    import { actions, reducer, view as TodoList } from './xxxx'
  • 相关阅读:
    Flask之路由(app.route)详解
    Linux 文件/目录访问(opendir/closedir/readdir)
    公牛10-11德里克·罗斯最强赛季记录
    c++ 线程类
    docker打包发版
    【算法刷题日记之本手篇】完全数计算与扑克牌大小
    移动应用-Android开发基础\核心知识点
    解决使用`npm install`或`npm i`命令之后报`Unexpected token in JSON at position`错误的问题
    Redis - 10、主从复制
    java毕业设计选题系统ssm实现的商城系统(电商购物项目)
  • 原文地址:https://blog.csdn.net/Ming_xm/article/details/133944937