七、Redux

郁子大约 12 分钟约 3509 字笔记React16.8尚硅谷张天禹React Redux

(一)Redux 理解

1.学习文档

2.Redux 是什么

  • Redux 是一个专门用于做 状态管理 的 JS 库(不是 React 插件库)
  • 可以用在 React、Angular、Vue 等项目中,但基本与 React 配合使用
  • 作用:集中式管理 React 应用中多个组件 共享 的状态

3.使用场景

  • 某个组件的状态需要让其他组件可以随时拿到(共享)
  • 一个组件需要改变另一个组件的状态(通信)
  • 总体原则:能不用就不用,如果不用比较吃力才考虑使用

4.工作流程

(二)Redux 三个核心概念

1.action

  • 动作的对象
  • 包含 2 个属性
    • type:标识属性,值为字符串,唯一,必要属性
    • data:数据属性,值类型任意,可选属性
{
  type: 'ADD_STUDENT',
  data: {
    name: 'Tom',
    age: 18
  }
}

2.reducer

  • 用于初始化状态、加工状态
  • 加工时,根据旧的 stateaction ,产生新的 state纯函数

3.store

  • stateactionreducer 联系在一起的对象

1)如何得到该对象?

import { createStore } from "redux";
import reducer from "./reducers";
const store = createStore(reducer);

2)此对象的功能?

  • getState()
    • 得到 state
  • dispatch(action)
    • 分发 action ,触发 reducer 调用,产生新的 state
  • subscribe(listener)
    • 注册监听,当产生了新的 state 时,自动调用

(三)Redux 的核心 API

1.createStore()

  • 作用:创建包含指定 reducerstore 对象

2.store 对象

  • 作用:Redux 库最核心的管理对象
  • 内部维护着 statereducer
  • 核心方法
    • getState()
    • dispatch(action)
    • subscribe(listener)
store.getState();
store.dispatch({
  type: "INCREMENT",
  number,
});
store.subscribe(render);

3.applyMiddleware()

  • 作用:应用上基于 redux 的中间件(插件库)

4.combineReducers()

  • 作用:合并多个 reducer 函数

(四)Redux 异步编程

1.理解

  • redux 默认是不能进行异步处理的
  • 某些时候应用中需要 redux 中执行异步任务
    • 如:ajax 请求、定时器

2.使用异步中间件

npm install --save redux-thunk

(五)react-redux 库

1.理解

  • 一个 Facebook 公司出品的 react 插件库
  • 专门用来简化 react 应用中使用 redux

2.react-redux 将所有组件分成两大类

1)UI 组件

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 通过 props 接收数据(一般数据和函数)
  • 不使用任何 redux 的 API
  • 一般保存在 components 文件夹下

2)容器组件

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 使用 redux 的 API
  • 一般保存在 containers 文件夹下

3.相关 API

1)Provider

  • 让所有组件都可以得到 state 数据
<Provider store={store}>
  <App />
</Provider>

2)connect

  • 用于包装 UI 组件生成容器组件
import { connect } from "react-redux";
import Counter from "./components/Count";

connect(mapStateToProps, mapDispatchToProps)(Counter);

3)mapStateToProps

  • 将外部的数据(即 state 对象)转换为 UI 组件的标签属性
const mapStateToProps = function (state) {
  return {
    value: state,
  };
};

4)mapDispatchToProps

  • 将分发 action 的函数转换为 UI 组件的标签属性

(六)使用 redux 调试工具

1.安装 Chrome 浏览器插件

2.项目工程中下载工具依赖包

npm install --save-dev redux-devtools-extension

3. store.js 中配置

// 引入redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension";

// 暴露store
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)));

(七)纯函数和高阶函数

1.纯函数

  • 是一类特别的函数
    • 只要是同样的输入(实参),必定得到同样的输出(返回值)
  • 必须遵守以下约束
    • 函数体内不得改写参数数据
    • 不会产生任何副作用
      • 例如发送网络请求、调用输入和输出设备等
    • 不能调用 Date.now() 或者 Math.random() 等不纯的方法
  • reduxreducer 函数必须是一个纯函数
// /src/redux/reducers/person.js
export default function personReducer(preState = initState, action) {
  // console.log("personReducer");
  const { type, data } = action;
  switch (type) {
    // 如果是添加一个人
    case ADD_PERSON:
      /**
       * 1.redux浅比较后发现数组地址未改变,所以不进行页面更新,应该生成一个新的数组而不是使用数组七大API操作原数组
       * 2.同时这样写修改了参数数据(preState),破坏了纯函数的约束,reducer必须是一个纯函数
       */
      // preState.unshift(data);
      // return preState;

      // 正确写法:生成新数组
      return [data, ...preState];
    default:
      return preState;
  }
}

2.高阶函数

1)理解

  • 是一类特别的函数
  • 情况 1:参数是函数
  • 情况 2:返回值是函数

2)常见的高阶函数

  • 定时器设置函数
  • 数组的 forEach() / map() / filter() / reduce() / find() / bind()
  • Promise
  • react-redux 中的 connect 函数

3)作用

  • 能实现更加动态、更加可扩展的功能

(八)求和案例

1.redux 精简版

1)去除 Count 组件自身的状态

2)src 下建立以下文件

  • /src/redux/store.js
  • /src/redux/count_reducer.js

3)store.js

  • 引入 redux 中的 createStore 函数,创建一个 store
  • createStore 调用时要传入一个为其服务的 reducer
  • 记得暴露 store 对象
// /src/redux/store.js
/**
 * 该文件专门用于暴露一个store对象,整个应用只有一个store对象
 */
// 引入createStore,专门用于创建redux中最为核心的store对象
import { legacy_createStore as createStore } from "redux";
// 引入为Count组件服务的reducer
import countReducer from "./count_reducer";
// 暴露store
export default createStore(countReducer);

4)count_reducer.js

  • reducer 本质是一个函数,接收: preStateaction ,返回加工后的状态
  • reducer 有两个作用:初始化状态、加工状态
  • reducer 第一次被调用时,是 store 自动触发的
    • 传递的 preStateundefined
    • 传递的 action{ type: '@@REDUX/INIT_a.2.b.4' }
// /src/redux/count_reducer.js
/**
 * 1.该文件是用于创建一个为Count组件服务的reducer,reducer本质是一个纯函数
 * 2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
 */
// 初始化状态
const initState = 0;
// 暴露reducer
export default function countReducer(preState = initState, action) {
  // 从action对象中获取:type、data
  const { type, data } = action;
  // 根据type决定如何加工数据
  switch (type) {
    // 如果是加
    case "increment":
      return preState + data;
    // 如果是减
    case "decrement":
      return preState - data;
    // 初始化
    default:
      return preState;
  }
}

5)在 index.js 中监测 store 中状态的改变

  • 一旦发生改变重新渲染 <App/>
  • redux 只负责管理状态,至于状态的改变驱动页面的展示,需要自己编写
// /src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 全局监听redux状态改变
import store from "./redux/store";

ReactDOM.render(<App />, document.getElementById("root"));

// 监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(() => {
  ReactDOM.render(<App />, document.getElementById("root"));
});

2.redux 完整版

1)新增 count_action.js

  • 专门用于创建 action 对象
// /src/redux/count_action.js
/**
 * 该文件专门为Count组件生成action对象
 */
import { INCREMENT, DECREMENT } from "./constant";
export const createIncrementAction = (data) => ({ type: INCREMENT, data });
export const createDecrementAction = (data) => ({ type: DECREMENT, data });

2)新增 constant.js

  • 放置容易写错的 type
// /src/redux/constant.js
/**
 * 该模块是用于定义action对象中type类型的常量值
 */
export const INCREMENT = "increment";
export const DECREMENT = "decrement";

3)修改 count_reducer.js

// /src/redux/count_reducer.js
import { INCREMENT, DECREMENT } from "./constant";
const initState = 0;
export default function countReducer(preState = initState, action) {
  const { type, data } = action;
  switch (type) {
    case INCREMENT:
      return preState + data;
    case DECREMENT:
      return preState - data;
    default:
      return preState;
  }
}

3.redux 异步 action 版

1)使用场景

  • 延迟的动作不想交给组件自身,想交给 action

2)何时需要

  • 想要对状态进行操作,但是具体的数据靠异步任务返回

3)具体编码

  • yarn add redux-thunk,并配置在 store
  • 创建 action 的函数不再返回一般对象,而是一个函数,该函数中写 异步任务
    • 异步任务有结果后,分发一个 同步action 去真正操作数据

相关信息

异步 action 不是必须要写的

完全可以自己等待异步任务的结果再去分发同步 action

// /src/components/Count/index.jsx
import { createIncrementAsyncAction } from "../../redux/count_action";
incrementAsync = () => {
  const { value } = this.selectValue;
  store.dispatch(createIncrementAsyncAction(value * 1, 500));
};
// /src/redux/count_action.js
import { INCREMENT, DECREMENT } from "./constant";
// 同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = (data) => ({ type: INCREMENT, data });
export const createDecrementAction = (data) => ({ type: DECREMENT, data });
/**
 * 异步action,就是指action的值为函数,因为函数体内能开启异步任务
 * 异步action中一般会调用同步action
 * 异步action不是必须要用的
 */
export const createIncrementAsyncAction = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(createIncrementAction(data));
    }, time);
  };
};
// /src/redux/store.js
import { legacy_createStore as createStore, applyMiddleware } from "redux";
// 引入为Count组件服务的reducer
import countReducer from "./count_reducer";
// 引入redux-thunk用于支持异步action
import thunk from "redux-thunk";
// 暴露store
export default createStore(countReducer, applyMiddleware(thunk));

4.react-redux 基本使用

1)明确两个概念

  • UI 组件
    • 不能使用任何 redux 的 API
    • 只负责 页面的呈现、交互
  • 容器组件
    • 负责和 redux 通信
    • 将结果交给 UI 组件

2)如何创建一个容器组件

  • 使用 react-reduxconnect 函数
    • connect(mapStateToProps, mapDispatchToProps)(UI组件)
  • mapStateToProps
    • 映射状态,返回值是一个对象
  • mapDispatchToProps
    • 映射操作状态的方法,返回值是一个对象

相关信息

容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入

mapDispatchToProps 也可以是一个对象

// /src/components/Count/index.jsx
incrementOdd = () => {
  const { value } = this.selectValue;
  if (this.props.count % 2 !== 0) {
    this.props.jia(value * 1);
  }
};
// /src/App.jsx
import React, { Component } from "react";
import Count from "./containers/Count";
// 引入store
import store from "./redux/store";
export default class App extends Component {
  render() {
    return (
      <div>
        {/* 给容器组件传递store */}
        <Count store={store} />
      </div>
    );
  }
}
// /src/containers/Count/index.jsx
// 引入Count的UI组件
import CountUI from "../../components/Count";
// 引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from "../../redux/count_action";
// 引入connect用于连接UI组件与redux
import { connect } from "react-redux";

/**
 * 1.mapStateToProps函数用于传递状态
 * 2.mapStateToProps函数返回的是一个对象
 * 3.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
 */
const mapStateToProps = (state) => {
  return {
    count: state,
  };
};

/**
 * 1.mapDispatchToProps函数用于传递操作状态的方法
 * 2.mapDispatchToProps函数返回的是一个对象
 * 3.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
 */
const mapDispatchToProps = (dispatch) => {
  return {
    jia: (number) => dispatch(createIncrementAction(number)),
    jian: (number) => dispatch(createDecrementAction(number)),
    jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time)),
  };
};

// 使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);

5.react-redux 代码优化版

1)mapDispatchToProps 的简写

// /src/containers/Count/index.jsx
// 引入Count的UI组件
import CountUI from "../../components/Count";
// 引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from "../../redux/count_action";
// 引入connect用于连接UI组件与redux
import { connect } from "react-redux";

// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
  (state) => ({ count: state }),

  // mapDispatchToProps写法一:一般写法,是一个函数
  // (dispatch) => ({
  //   jia: (number) => dispatch(createIncrementAction(number)),
  //   jian: (number) => dispatch(createDecrementAction(number)),
  //   jiaAsync: (number, time) =>
  //     dispatch(createIncrementAsyncAction(number, time)),
  // })

  // mapDispatchToProps写法二:简写,是一个对象
  {
    jia: createIncrementAction,
    jian: createDecrementAction,
    jiaAsync: createIncrementAsyncAction,
  },
)(CountUI);

2)入口文件

  • 不需要监听页面状态改变, react-redux 的容器组件自动监测
  • 使用 ProviderApp 下的所有组件一次性提供 storeApp 组件不需要给容器组件传递 store
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 引入store
import store from "./redux/store";
import { Provider } from "react-redux";

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root"),
);
// /src/App.jsx
import React, { Component } from "react";
import Count from "./containers/Count";

export default class App extends Component {
  render() {
    return (
      <div>
        <Count />
      </div>
    );
  }
}

3)项目文件层级

  • UI 组件和容器组件整合成一个 jsx 文件
// /src/containers/Count/index.jsx
import React, { Component } from "react";
// 引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from "../../redux/count_action";
// 引入connect用于连接UI组件与redux
import { connect } from "react-redux";

// 定义UI组件
class Count extends Component {
  state = {
    hobby: "看日漫",
  };

  increment = () => {
    const { value } = this.selectValue;
    this.props.jia(value * 1);
  };

  decrement = () => {
    const { value } = this.selectValue;
    this.props.jian(value * 1);
  };

  incrementOdd = () => {
    const { value } = this.selectValue;
    if (this.props.count % 2 !== 0) {
      this.props.jia(value * 1);
    }
  };

  incrementAsync = () => {
    const { value } = this.selectValue;
    this.props.jiaAsync(value * 1, 500);
  };

  render() {
    // console.log("UI组件接收到的props是:", this.props);
    return (
      <div>
        <h2>当前求和为:{this.props.count}</h2>
        <select ref={(c) => (this.selectValue = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        &nbsp;<button onClick={this.increment}>+</button>
        &nbsp;<button onClick={this.decrement}>-</button>
        &nbsp;<button onClick={this.incrementOdd}>奇数加</button>
        &nbsp;<button onClick={this.incrementAsync}>异步加</button>
      </div>
    );
  }
}

// 使用connect()()创建并暴露一个Count的容器组件
export default connect((state) => ({ count: state }), {
  jia: createIncrementAction,
  jian: createDecrementAction,
  jiaAsync: createIncrementAsyncAction,
})(Count);

4)一个组件要和 reducer “打交道”要经过哪几步?

  • 定义好 UI 组件 —— 不暴露
  • 引入 connect 生成一个容器组件,并暴露
  • 在 UI 组件中通过 this.props.xxxxx 读取和操作状态
connect(
  (state) => ({ key: value }), // 映射状态
  {
    key: xxxxAction,
  }, // 映射操作状态的方法
)(UI组件);

6.react-redux 数据共享版

  • 定义一个 Person 组件,和 Count 组件通过 redux 共享数据
  • Person 组件编写: reduceraction ,配置 constant 常量
  • Person 的 reducer 和 Count 的 reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象
  • 交给 store 的是总的 reducer ,最后注意在组件汇总取出状态的时候,记得“取到位”
// /src/redux/store.js
/**
 * 该文件专门用于暴露一个store对象,整个应用只有一个store对象
 */
// 引入createStore,专门用于创建redux中最为核心的store对象
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from "redux";
// 引入为Count组件服务的reducer
import countReducer from "./reducers/count";
// 引入为Person组件服务的reducer
import personReducer from "./reducers/person";
// 引入redux-thunk用于支持异步action
import thunk from "redux-thunk";

// combineReducers传入的对象就是redux保存的总状态对象
// 汇总所有reducers
const allReducer = combineReducers({
  he: countReducer,
  rens: personReducer,
});

// 暴露store
export default createStore(allReducer, applyMiddleware(thunk));
// /src/containers/Count/index.jsx
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
  (state) => ({
    count: state.he,
    renshu: state.rens.length,
  }),
  {
    jia: createIncrementAction,
    jian: createDecrementAction,
    jiaAsync: createIncrementAsyncAction,
  },
)(Count);
// /src/containers/Person/index.jsx
export default connect(
  (state) => ({
    yiduiren: state.rens,
    qiuhe: state.he,
  }),
  {
    jiaYiRen: createAddPersonAction,
  },
)(Person);

7.react-redux 最终版

  • 所有变量名字要规范,尽量触发对象的简写形式
  • reducers 文件夹中,编写 index.js 专门用于汇总并暴露所有的 reducer
// /src/redux/reducers/index.js
/**
 * 该文件用于汇总所有的reducer为一个总的reducer
 */

// 引入combineReducers用于汇总多个reducer
import { combineReducers } from "redux";
// 引入为Count组件服务的reducer
import count from "./count";
// 引入为Person组件服务的reducer
import persons from "./person";

// combineReducers传入的对象就是redux保存的总状态对象
// 汇总所有reducers
export default combineReducers({
  count,
  persons,
});
// /src/redux/store.js
// 引入汇总之后的reducer
import reducers from "./reducers";
// 暴露store
export default createStore(reducers, composeWithDevTools(applyMiddleware(thunk)));
上次编辑于: