七、Redux
大约 12 分钟约 3509 字
(一)Redux 理解
1.学习文档
- 英文文档: https://redux.js.org/
- 中文文档: http://www.redux.org.cn/
- Github: https://github.com/reactjs/redux
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
- 用于初始化状态、加工状态
- 加工时,根据旧的
state
和action
,产生新的state
的 纯函数
3.store
- 将
state
、action
、reducer
联系在一起的对象
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()
- 作用:创建包含指定
reducer
的store
对象
2.store 对象
- 作用:Redux 库最核心的管理对象
- 内部维护着
state
、reducer
- 核心方法
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
store.js
中配置
3. // 引入redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension";
// 暴露store
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)));
(七)纯函数和高阶函数
1.纯函数
- 是一类特别的函数
- 只要是同样的输入(实参),必定得到同样的输出(返回值)
- 必须遵守以下约束
- 函数体内不得改写参数数据
- 不会产生任何副作用
- 例如发送网络请求、调用输入和输出设备等
- 不能调用
Date.now()
或者Math.random()
等不纯的方法
redux
的reducer
函数必须是一个纯函数
// /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 精简版
Count
组件自身的状态
1)去除 src
下建立以下文件
2)/src/redux/store.js
/src/redux/count_reducer.js
store.js
3)- 引入
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);
count_reducer.js
4)reducer
本质是一个函数,接收:preState
、action
,返回加工后的状态reducer
有两个作用:初始化状态、加工状态reducer
第一次被调用时,是store
自动触发的- 传递的
preState
是undefined
- 传递的
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;
}
}
index.js
中监测 store
中状态的改变
5)在 - 一旦发生改变重新渲染
<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 完整版
count_action.js
1)新增 - 专门用于创建
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 });
constant.js
2)新增 - 放置容易写错的
type
值
// /src/redux/constant.js
/**
* 该模块是用于定义action对象中type类型的常量值
*/
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
count_reducer.js
3)修改 // /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-redux
的connect
函数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
的容器组件自动监测 - 使用
Provider
为App
下的所有组件一次性提供store
,App
组件不需要给容器组件传递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>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementOdd}>奇数加</button>
<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
组件编写:reducer
、action
,配置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)));