一、概述
1、redux 是什么?
(1)redux 是一个专门用于做状态管理的 JS 库 ( 不是 react 插件库 )。
(2)它可以用在 react、angular、vue等项目中, 但基本与 react 配合使用。
(3)作用:集中式管理 react 应用中多个组件共享的状态。
学习文档:
(1)英文文档:Redux - A predictable state container for JavaScript apps. | Redux
(2)中文文档:Redux中文文档
(3)github:https://github.com/reactjs/redux
2、什么情况下需要使用 redux?
(1)某个组件的状态,需要让其他组件可以随时拿到(共享)。
(2)一个组件需要改变另一个组件的状态(通信)。
(3)总体原则:能不用就不用,如果不用比较吃力才考虑使用。
3、redux 工作流程
流程说明:
(1)React Components 是组件,用于实现某种功能,呈现某种效果。
(2)Action Creators 在 redux 中派发 action 方法,action 通过 store 的 dispatch 方法派发给 Store。
(3)Store 接收 action,连同之前的 previousState,一起传递给 Reducers。
(4)Reducers 将处理后的数据返回给 Store。
(5)Store 改变自身的 state,组件可以通过 store.getState() 方法获取到 state。
餐厅例子说明:
(1)【React Components — 客人】、【Action Creators — 服务员】、
【Store — 老板】、【Reducers — 厨师】。
(2)客人来餐厅点餐,告诉服务员具体要求(什么菜系,菜名),服务员将记录下的菜单 (action) 拿给餐厅老板,老板反手交代厨师去做菜。厨师做好菜之后,将菜拿到前台老板处,老板叫号让客人过来取。
二、案例( 基于 redux )
1、纯 react 版
/* components/Count/index.jsx */
import React, { Component } from 'react'
export default class Count extends Component {
state = { count: 0 }
//加法
increment = () => {
const { value } = this.selectNumber
const { count } = this.state
this.setState({ count: count + value * 1 })
}
//减法
decrement = () => {
const { value } = this.selectNumber
const { count } = this.state
this.setState({ count: count - value * 1 })
}
//奇数再加
incrementIfOdd = () => {
const { value } = this.selectNumber
const { count } = this.state
if (count % 2 !== 0) {
this.setState({ count: count + value * 1 })
}
}
//异步加
incrementAsync = () => {
const { value } = this.selectNumber
const { count } = this.state
setTimeout(() => {
this.setState({ count: count + value * 1 })
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<select ref={c => this.selectNumber = 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.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
2、redux 精简版
2.1 src 代码结构
2.2 store.js
步骤:
(1)引入 redux 的 legacy_createStore 函数,创建一个 store。
(2)legacy_createStore 调用时要传入一个为其服务的 reducer。
(3)记得暴露 store对象。
// 引入 legacy_createStore 用于创建 redux 中最为核心的 store 对象
// 注:createStore 已被弃用
import { legacy_createStore } from 'redux';
// 引入为 Count 组件服务的 reducer
import countReducer from './count_reducer';
// 暴露 store
export default legacy_createStore(countReducer);
2.3 count_reducer.js
说明:
(1)reducer 的本质是一个函数,接收:preState、action,返回加工后的状态。
(2)reducer 有两个作用:初始化状态、加工状态、
(3)reducer 被第一次调用时,是store自动触发的,
传递的 preState 是 undefined、传递的 action 是:{type:'@@REDUX/INIT_a.2.b.4}。
const initState = 0;
export default function countReducer(prestate = initState, action) {
const { type, data } = action;
switch (type) {
case 'incNumber':
return prestate + data * 1;
default:
return prestate;
}
}
2.4 Count / index.js
import React, { Component } from 'react'
// 引入 store 用于获取 redux 中的状态
import store from '../redux/store';
export default class Count extends Component {
// 渲染界面的第一种方式,但是这种方式只能渲染当前的页面。
state = {};
componentDidMount() {
// 监测 redux 中状态的变化,只要状态变化,就会调用这个函数。
store.subscribe(() => {
// 这里并没有更新任何 this.state 的值,只是借用 state 来调用 render。
this.setState({});
})
}
// +
incNumber = () => {
const { value } = this.seleteNumber;
store.dispatch({ type: 'incNumber', data: value });
// store.dispatch(createIncNumber(value));
}
// 异步加
asynNumber = () => {
const { value } = this.seleteNumber;
setTimeout(() => {
store.dispatch({ type: 'incNumber', data: value });
}, 2000)
}
render() {
return (
<div>
<h4>当前求和为: {store.getState()}</h4><br />
<select ref={c => { this.seleteNumber = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<button onClick={this.incNumber}>+</button>
<button onClick={this.asynNumber}>异步加</button>
</div>
)
}
}
2.5 index.js
在 index.js 中监测 store 中状态的改变,一旦发生改变重新渲染 <App/>。
备注:redux 只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App'
import store from './component/redux/store';
const root = createRoot(document.getElementById('root'));
root.render(<App />)
// 第二种方式:当状态发生变化时,重新渲染 App 组件。
store.subscribe(() => {
root.render(<App />)
})
3、redux 完整版
redux 文件夹里的新增文件:
(1)count_action.js 专门用于创建 action 对象。
(2)constant.js 放置容易写错的 type 值。
// count_action.js
import { INC } from "./constant";
export function createIncAction(data) {
return { type: INC, data: data };
}
// constant.js
export const INC = 'incNumber';
4、异步 action 版
action 有两种形式: Object 类型的一般对象、函数。
同步 action:action 的值为 Object 类型的一般对象。
异步 action:action 的值为函数,异步 action 中一般都会调用同步 action,异步 action 不是必须要用的。
(1)明确:延迟的动作不想交给组件自身,想交给 action。
(2)何时需要异步 action:想要对状态进行操作,但是具体的数据靠异步任务返回。
(3)具体编码:
① npm i redux-thunk,并配置在 store 中。
② 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
③ 异步任务有结果后,分发一个同步的action去真正操作数据。
④ 备注:异步 action 不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
4.1 index.js
// index.js
asynNumber = () => {
const { value } = this.seleteNumber;
store.dispatch(createAsynAction(value * 1, 500));
// 原来的异步操作(在这部分直接等待结果)
// setTimeout(() => {
// store.dispatch({ type: 'incNumber', data: value });
// }, 500)
}
4.2 store.js
// 引入中间件的API: applyMiddleware
import { legacy_createStore, applyMiddleware } from 'redux';
import countReducer from './count_reducer';
// 引入中间件
import thunk from 'redux-thunk';
// 暴露 store, applyMiddleware(thunk) 作为第二个参数传入
export default legacy_createStore(countReducer, applyMiddleware(thunk));
4.3 count_action.js
import { INC } from "./constant";
export function createIncAction(data) {
return { type: INC, data: data };
}
export function createAsynAction(data, time) {
// 返回一个函数
return (dispatch) => {
setTimeout(() => {
dispatch(createIncAction(data));
}, time)
}
}
三、react-redux
1、概述
1.1 理解
- 一个react插件库。
-
专门用来简化react应用中使用redux。
1.2 react-redux 将所有组件分成两大类
(1)UI 组件
- 只负责 UI 的呈现,不带有任何业务逻辑。
- 通过props接收数据(一般数据和函数)。
- 不使用任何 Redux 的 API。
- 一般保存在components文件夹下。
(2)容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现。
- 使用 Redux 的 API。
- 一般保存在containers文件夹下。
总结:
(1)明确两个概念:
① UI组件:不能使用任何 redux 的 api,只负责页面的呈现、交互等。
② 容器组件:负责和 redux 通信,将结果交给 UI 组件。
(2)如何创建一个容器组件 — 靠 react-redux 的 connect 函数.
connect(mapStateToProps,mapDispatchToProps)(UI组件)
-mapStateToProps:映射状态,返回值是一个对象
-mapDispatchToProps:映射操作状态的方法,返回值是一个对象
(3)备注1:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入。
(4)备注2:mapDispatchToProps,也可以是一个对象。
2、案例(效果与上一节相同)
2.1 代码结构
2.2 containers / Count / index.js(容器组件)
说明:该组件为容器组件,个人理解是有两个作用:① 与 redux 通信(核心是store),使用 redux 的 API 获取状态(state)、操作状态(dispatch)。② 通过 connect 与 UI 组件绑定,为 UI 组件传递状态和操作状态的方法。
注意:容器组件中的 store 不能在文件中直接导入,而应该通过 App.js,也就是外部组件向容器组件传递 store。
// App.js import React, { Component } from 'react' import Count from './container/Count' import store from './redux/store' export default class App extends Component { render() { return ( <div> <Count store={store} /> </div> ) } }
// 引入 Count 的 UI 组件
import CountUI from '../../component/Count'
// 引入 redux 定义好的方法
import { createIncAction, createAsynAction } from '../../redux/count_actions'
// 引入 connect 用于连接 UI 组件与 redux
import { connect } from 'react-redux'
/*
1.mapStateToProps 函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapStateToProps 用于传递状态
*/
const mapStateToProps = (state) => {
return { count: state };
}
/*
1.mapDispatchToProps 函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapDispatchToProps 用于传递操作状态的方法
*/
const mapMethodToProps = (dispatch) => {
return {
createIncAction: (value) => { dispatch(createIncAction(value)) },
createAsynAction: (value, time) => { dispatch(createAsynAction(value, time)) }
}
}
/*
1.使用connect()()创建并暴露一个Count的容器组件
2.connect 接收两个参数(都是函数):
mapStateToProps 用于传递状态,mapMethodToProps用于传递操作状态的方法。
*/
export default connect(mapStateToProps, mapMethodToProps)(CountUI)
2.3 component / Count / index.js(UI 组件)
说明:这个文件是 Count 的 UI 组件,组件中不包含任何 redux 的 API,只关注于如果获取状态等最终呈现页面。
关于传递状态及操作状态的方法:容器组件与 UI 组件是父子组件,UI 组件通过 this.props 获取状态及操作状态的方法。
import React, { Component } from 'react'
export default class Count extends Component {
// +
incNumber = () => {
const { value } = this.seleteNumber;
this.props.createIncAction(value * 1);
}
// 异步加
asynNumber = () => {
const { value } = this.seleteNumber;
this.props.createAsynAction(value * 1, 500);
}
render() {
return (
<div>
<h4>当前求和为: {this.props.count}</h4><br />
<select ref={c => { this.seleteNumber = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<button onClick={this.incNumber}>+</button>
<button onClick={this.asynNumber}>异步加</button>
</div>
)
}
}
3、优化
3.1 简写 mapMethodToProps
mapMethodToProps 可以简化成用对象 { key: value } 的方式。因为 key 和 value 的名称相同,因此简写了。
/* 未简化方法 */
// const mapStateToProps = (state) => {
// return { count: state };
// }
// const mapMethodToProps = (dispatch) => {
// return {
// createIncAction: (value) => { dispatch(createIncAction(value)) },
// createAsynAction: (value, time) => { dispatch(createAsynAction(value, time)) }
// }
// }
// export default connect(const mapStateToProps, mapMethodToProps)(CountUI)
/* 简化方法 */
export default connect(
state => ({ count: state }),
{
createIncAction,
createAsynAction,
}
)(CountUI)
3.2 Provider 组件的使用
无需自己给容器组件传递 store,给 <App/> 包裹一个 <Provider store={store}> 即可。
import { Provider } from 'react-redux';
import store from './redux/store';
const root = createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
)
3.3 整合 UI 组件与容器组件
import { createIncAction, createAsynAction } from '../../redux/count_actions'
import { connect } from 'react-redux'
import React, { Component } from 'react'
class Count extends Component {
// +
incNumber = () => {
const { value } = this.seleteNumber;
this.props.createIncAction(value * 1);
}
// 异步加
asynNumber = () => {
const { value } = this.seleteNumber;
this.props.createAsynAction(value * 1, 500);
}
render() {
return (
<div>
<h4>当前求和为: {this.props.count}</h4><br />
<select ref={c => { this.seleteNumber = c }}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<button onClick={this.incNumber}>+</button>
<button onClick={this.asynNumber}>异步加</button>
</div>
)
}
}
export default connect(
state => ({ count: state }),
{
createIncAction,
createAsynAction,
}
)(Count)
3、多组件数据共享案例
笔记总结:
(1)定义一个 Pserson 组件,和 Count 组件通过 redux 共享数据。
(2)为 Person 组件编写:reducer、action,配置 constant 常量。
(3)重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!
(4)交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得“取到位”。
3.1 效果
有两个组件:Count 和 Person,实现组件间的数据共享。
3.2 代码结构
代码只说明数据共享的部分,完整代码放在资源:CSDN。(不需要积分)
3.3 store.js
import { legacy_createStore, applyMiddleware, combineReducers } from 'redux';
// 引入为 Count 组件服务的 reducer
import countReducer from './reducers/count_reducer';
// 引入为 Person 组件服务的 reducer
import personReducer from './reducers/person_reducer';
// 引入异步操作需要的模块
import thunk from 'redux-thunk';
const allReducer = combineReducers({
sum: countReducer,
personHandle: personReducer
})
// 暴露 store
export default legacy_createStore(allReducer, applyMiddleware(thunk));
3.4 container / Person / index.js
export default connect(
// 传递状态
state => ({
personArr: state.personHandle,
count: state.sum
}),
// 操作状态的方法
{
addPerson,
}
)(Person)
四、纯函数与高阶函数(一些知识点)
1、纯函数
(1)一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)。
(2)必须遵守以下一些约束:
① 不得改写参数数据。
② 不会产生任何副作用,例如网络请求,输入和输出设备。
③ 不能调用Date.now()或者Math.random()等不纯的方法。
(3)redux 的 reducer 函数必须是一个纯函数。【所以数组推入的方式使用的是 [ personObj, ...data ],不使用 arr.push 的方法(使用会导致页面数据不更新)】
2、高阶函数
(1)理解:一类特别的函数:
① 情况1:参数是函数。
② 情况2:返回是函数。
(2)常见的高阶函数:
① 定时器设置函数。
② 数组的 forEach() / map() / filter() / reduce() / find() / bind()。
③ promise。
④ react-redux 中的 connect 函数。
(3)作用:能实现更加动态,更加可扩展的功能。