redux
刀刀
1/7/2025
0 字
0 分钟
基础
概念
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件共享的状态。
作用
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
工作流程
action
- 动作的对象
- 包含2个属性
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
- 示例代码jsx
{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的
state
和action
, 产生新的state
的纯函数。
function reduxer(preState = 12, action) {
console.log(preState, action)
const { type, data } = action
switch (type) {
case 'add':
return preState + data
case 'decrement':
return preState - data
case 'chen':
return preState * data
case 'chu':
return preState / data
default:
return preState
}
}
export default reduxer
store
- 将state、action、reducer联系在一起的对象
- 如何得到此对象?
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
jsximport { createStore } from "redux"; import reduxer from './reduxer' const store = createStore(reduxer) export default store
- 此对象的功能?
- getState(): 得到state
- dispatch(action): 分发action, 触发reducer调用, 产生新的state。注意:他只负责返回新的 state,不负责页面更新,因此此时页面还是旧数据。
- subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
jsximport React, { Component } from 'react' import store from "../redux/store"; export default class Count extends Component { componentDidMount() { store.subscribe(() => { this.setState({}) }) } addFn = () => { const {value} = this.selectNum store.dispatch({type: 'add', data: value * 1}) } addOddFn = () => { const {value} = this.selectNum const count = store.getState() if(count % 2 !== 0) { store.dispatch({type: 'add', data: value * 1}) } } addAsyncFn = () => { const {value} = this.selectNum setTimeout(() => { store.dispatch({type: 'add', data: value * 1}) }, 500); } render() { return ( <div> <h1>当前和为:{store.getState()}</h1> <select ref={c => this.selectNum = c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.addFn}>+</button> <button onClick={this.addOddFn}>奇数加</button> <button onClick={this.addAsyncFn}>异步加</button> </div> ) } }
redux的核心API
createstore()
作用:创建包含指定 reducer
的 store
对象
store对象
- 作用: redux库最核心的管理对象
- 它内部维护着:
- state
- reducer
- 核心方法:
- getState()
- dispatch(action)
- subscribe(listener)
- 具体编码:
- store.getState()
- store.dispatch({type:'INCREMENT', number})
- store.subscribe(render)
applyMiddleware()
作用:应用上基于 redux
的中间件(插件库)
使用:
引入插件
yarn add redux-thunk
引入
applyMiddleware
方法与redux-thunk
方法jsximport { applyMiddleware } from 'react' import thunk from 'redux-thunk'
注册方法
diff- export default createStore(redux) + export default createStore(redux, applyMiddleware(thunk))
创建异步的方法
jsxexport const createAction = (data, time) => { return (dispatch) => { setTimeout(() => { dispatch({type: 'xxx', data}) }, time) } }
官方文档说了,开启了中间件后,
dispatch()
如果发现action
是一个函数,会帮你封装。上方的代码中,刚好return
返回一个函数,因此无需手动调用store
。异步
action
不是必须要用的。使用
jsxuseActionFn = () => { store.dispatch(createAction(1, 500)) }
combineReducers()
作用:合并多个reducer函数
react-redux
理解
- 一个
react
插件库 - 专门用来简化react应用中使用
redux
模型图
- 所有的 UI 组件都应该被一个容器组件包裹,他们是父子关系
- 容器组件真正和
redux
打交道,里面可以使用redux
的 API - UI 组件中不能使用
redux
的 API - 容器组件会传给 UI 组件以下数据:
redux
中保存的状态- 用于操作的状态
- 容器给 UI 传递状态、操作状态的方法,均通过
props
传递
分类
react-Redux
将所有组件分成两大类
- UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过
props
接收数据(一般数据和函数) - 不使用任何 Redux 的 API
- 一般保存在
components
文件夹下
- 容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在
containers
文件夹下
相关API
- Provider:让所有组件都可以得到state数据
- connect:用于包装 UI 组件生成容器组件
- mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
- mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
容器组件的创建
安装
react-redux
yarn add react-redux
引入之前的组件(现在是作为 UI 组件)
jsximport CountUI from '../../components/Count'
引入
connect
方法,连接 UI 组件与redux
jsximport { connect } from 'react-redux'
使用
jsxconst CountContainer = connect()(CountUI)
上方代码看出两个信息:
connect
是一个函数- 其返回值也是一个函数
建立联系固定写法是在返回的函数中传参需要连接的 UI 组件。
导出
jsxexport default CountContainer
总体代码:
import CountUI from '../../components/Count'
import { connect } from 'react-redux'
const CountContainer = connect()(CountUI)
export default CountContainer
容器组件的连接
容器组件需要使用 store
,但是不能自己直接引入,而是要在父组件传入来,代码如下:
import React, { Component } from 'react'
import Count from '../comtainers/Count'
import store from '../redux/store'
export default class App extends Component {
render() {
return {
<div>
// 给容器组件传递store
<Count store={store} />
</div>
}
}
}
容器组件传递的 store
给 UI 组件使用,该如何实现呢?前面组件传值都是 <A a="1" />
的 key
和 value
形式传参,但是 UI 组件这里不适用,需要通过函数的返回值作为状态传递给 UI 组件,代码如下:
import CountUI from '../../components/Count'
import { connect } from 'react-redux'
function mapStateToProps() {
return {key: 'value'}
}
function mapDispatchToProps() {
return {
fn: () => console.log(1),
func: () => console.log(2)
}
}
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)
export default CountContainer
- 返回的
key
作为传递给 UI 组件props
的key
,value
作为 UI 组件props
的value
——状态。- 返回的
key: fn
作为传递给 UI 组件的key
,作为 UI 组件props
的value
——操作状态的方法。
此时去往 UI 组件打印 this.props
,能够接收到一个对象,其中包含 APP 组件传递的 store
、connect
连接时接收的对象 key
和方法 fn
。
在函数中如果想要使用 store
内的变量,只需要通过形参的方式接收状态 state
即可。代码如下所示:
function mapStateProps(state) {
return {key: state}
}
UI 组件调用函数方法时,就触发传过去的函数的回调。因此 UI 组件调用方法传参,在 return
的回调函数中通过形参接收。函数中可以直接获取操作状态 dispatch
,代码如下:
mapDispatchToPropsfunction mapDispatchToProps(dispatch) {
return {
fn: (number) => dispatch({type: 'add', data: number}),
func: (number) => dispatch({type: 'lose', data: number}),
}
}
坑
- 如果状态不传对象而传其他类型,会报错,提示你需要返回一个对象
- 如果父组件不通过
store={store}
传递store
,运行后会报错没有store
代码优化
简写mapDispatchToProps
connect
方法中可以直接传函数,代码如下:
export default connect(
state => ({count: state}),
dispatch => ({
fn: num => dispatch({type: 'add', data: num})
})
)(CountUI)
其中,第二个参数又可以省略,不用传一个函数,直接传一个对象,代码如下:
export default connect(
state => ({count: state}),
{
fn: data => ({type: 'add', data})
}
)(CountUI)
原理:
对象这个写法,相当于为 UI 组件的 props
传递一个名为 fn
的 action
函数,UI 组件调用 fn
后实际上就是调用 data => ({type: 'add', data})
这个箭头函数。
然后好像在这里就停住了,他无法调用 dispatch
。这里我们只需要做到这里就好了,react-redux
在底层帮我们处理了,我们只需要给他一个 action
函数,他底部会帮我们调用 dispatch
。
Provider组件的使用
在使用 redux
时,需要使用 store.subscribe()
监测变化更新视图,代码如下:
store.subscribe(() => {
ReactDOM.render(<App/>, document.getElementById('root'))
})
使用了 react-redux
后,我们不再需要 store.subscribe()
监测也能实现效果了。原理是容器组件是通过 connect()()
创建建立连接的,因此底部已经检测了,不需要我们再监测了。
前面使用一个容器组件时,通过为标签添加 store={store}
实现传递 store
。但是如果多个容器组件时,一个一个写显然不太合理,可以直接使用 Provider
方法统一传递 store
对象,代码如下:
<Provider store={store}>
<App />
</Provider>
整合组件
UI 组件和容器组件分开两个文件后期容易文件冗余不好维护,把两个组件整合到一起作为一个组件。UI 组件无需暴露,容器组件与 UI 组件建立连接后暴露容器组件即可。代码如下所示:
import React, { Component } from 'react'
import { connect } from "react-redux";
class Count extends Component {
handleAddFn = () => {
this.props.add(5)
}
render() {
return (
<>
<div>当前求和为:{this.props.count}</div>
<button onClick={this.handleAddFn}>加一</button>
</>
)
}
}
export default connect(
(state) => ({count: state}),
{
add: data => ({type: 'add', data})
}
)(Count)
数据共享
如果有多个 reducer
需要共享数据,上面的方法显然不合适,他只能挂载一个 reducer
。要如何挂载多个状态呢?我们都知道,js 中能挂载多个变量的方法只有数组和对象,而相对而言,对象是更优于数组的。
这个时候需要使用到 combineReducers
,combine
翻译成中文就是合并,其是一个函数,往里面传一个对象,代码如下:
import { createStore, applyMiddleware, combineReducers } from "redux";
import count_redux from './reduxer'
import person_reduxer from './reduxerperson'
const a = combineReducers({
pre: person_reduxer,
count: count_redux
})
const store = createStore(a)
export default store
现在,store
的状态就有了两个属性可以使用,运行一下看看效果,发现报错,报错信息如下所示:
在 Count
组件中,我们写了这么一段代码 (state) => ({count: state})
。在之前没有做数据共享状态合并前他只是一个普通的变量,因此可以直接使用。而现在做了共享合并后,state
已经是合并状态了,即是 {pre: person_reduxer, count: count_redux}
。因此在容器组件中需要通过对象点语法来获取。代码如下:
export default connect(
(state) => ({count: state.count}),
{
add: data => ({type: 'add', data})
}
)(Count)
如果想要使用其他 reducer
内的变量可以在 state
中获取,直接在 state
箭头函数中返回的对象内添加即可。
纯函数
- 不得改写参数的数据,如
arr.unshift()
- 不会产生任何副作用,例如网络请求、输入和输出设备
- 不能调用
Date.now()
或Math.random()
这种可以得到不同输出的方法
redux 内的 reducer
必须是要纯函数。实际业务中,不纯的业务只能放到 action
中。
总结
概念
- UI组件:不能使用任何
redux
组件,只负责页面呈现、交互 - 容器组件:负责和
redux
通信,把结果给 UI 组件呈现
- UI组件:不能使用任何
创建
connect(mapStateToProps, mapDispatchToProps)(UI组件)
。其中:- mapStateToProps:映射状态,返回是一个对象
- mapDispatchToProps:映射操作状态的方法,返回值是一个对象函数
数据共享
- 数据共享后的
reducer
要使用combineReducers
进行合并,合并后是一个对象 - 交给
store
的是总reducer
,在组件内使用状态时要通过点语法获取对象内的值
- 数据共享后的
注意事项
- 容器组件中的
store
是拿props
传递去的,而不是容器组件中直接引入 mapDispatchToProps
可以是一个函数,也可以是一个对象- 数据共享时如果发现数据地址相同,则不会更新视图(如数组的
push
、unshift
等方法)。因此需要改变地址来达到视图更新的效果
- 容器组件中的
redux调试工具
通过下载 redux-devtools
工具进行调试(谷歌需要使用梯子去谷歌商店下载)
新版 import { configureStore } from '@reduxjs/toolkit'
打包
通过 npm run build
打包,打包后的文件不可直接打开预览,需要放到服务器中。可下载第三方库 serve
创建静态服务器。
- 下载
npm i serve -g
- 使用
在需要开启的根目录文件夹的终端下输入命令 serve
即可
serve
如果在该文件夹下创建一个 a
文件夹,并以该 a
文件夹作为服务器开启,则通过以下的方式开启即可
serve a
# 或
serve ./a