什么是状态管理
React状态管理简介
什么是状态管理?
状态管理工具的本质:管理共享内存中的状态
- 共享内存
- 管理状态
- 页面通信
- 组件通信
- 刷新失效?
详细定义: 单页应用的各个组件本身是共享内存的,如果将状态保存在内存中 就可以读写统一内存中的变量,从而达到状态共享的目的。
为什么React有这么多状态管理工具 ?
- Vue: Vuex(Pinia)
- Angular: Service和和Rxis
- React: Flux、Redux、Mobx、Rxis、 Recoil、 Jotai、 Zustand
跟不同前端框架的定义有关,Vue和Angular双向数据绑定,计算属性等,数据是响应式的控制视图刷新,拥有计算属性等,这些使得Vue和Angular需要状态管理的场景减少,此外其本身就包含了完整的状态管理工具,比如Vue的Vuex和Pinia,Angular的Service(RXis)等,从官方定调而React不一样,React是一个纯UI层的前端框架,UI = fnstate),React将状态的变动完全交给开发者。
状态管理 管理工具简介
React状态管理工具可以分为以下几类
- React自带: Local State(props) 和Context
- 单白数据流: Flux、Redux(Redux-toolkit,
- 双向数据绑定:Mobx
- 原子型状态管理:Recoil、Jotai
- 异步操作密集型: Rxis
每一种状态管理工具都有其不同的适用性,不同场景下需要合理的选择状态管理工具
Local State(props): local State顾名思义,就是组件级别的局部状态,当组件创建时初始化和生效,组件销毁时生效。
Context: React中的Context解决了react中,props或者state进行多级数据传递,则数据需要自顶下流经过每一级组件,无法跨级的问题。但是Context在页面间共享数据的时候同样有很多问题。
- Context相当于全局变量, 难以追溯数据的变更情况
- 使用Context的组件内部耦合度太高,不利于组件的复用和单元测试
- 会产生不必要的更新(比如会穿透memo和dependicies等
- Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合
- 粒度也不太好控制,不能细粒度的区分组件依赖了哪一个
- Context多个Context会存在层层嵌套的问题
Redux
Redux 是一个小型的独立 JS 库,Redux 可以与任何 UI 框架集成,常与 React 一起使用,因此Redux官方提供了React-Redux的包,来让React组件通过读取状态与调度操作(更新Store)与React Store交互。
Redux设计思想:
- 视图与状态是一一对应的
- 所有的状态,保存在一个对象里面
Redux的核心:
createStore:创建一个Redux Store存储combineReducers:将多个不同的Reducer函数,包装成一个Reducer,它将调用每个child Reducer,然后把它们的结果收集到一个状态对象中。applyMiddleware:将中间件应用到Store增强器,比如异步状态获取。compose:将多个Store增强器合并为一个Store增强器
React Store就是保存数据的地方,可以认为它是一个容器,整个应用只能有一个Store
///计数器组件
const Counter = (params) => {
const [count, setCount] = useState(0)
return (
<div>
<div> 当前计数为:{count} </div>
<button onClick={() => {setCount(count + 1)}}>计数加1</button>
<button onClick={() => {setCount(count + 2)}}>计数加2</button>
</div>
)
}
复制代码
计数器组件采用redux进行状态管理
///第1步,创建Store对象
import { createStore } from "redux";
///函数`createStore`的参数之一,初始状态
const initValues = {
count: 12,
}
///函数`createStore`的参数之一,Reducer函数(两参数,一个状态,一个更新状态的函数,like 高阶函数reduce)
const CounterReduce = (state, action) => {
switch (action.type) {
case 'incrementOne':
return {...state,count: state.count + 1}
case 'incrementTwo':
return {...state,count: state.count + 2}
default:
return state
}
}
///创建完毕
const store = createStore(counterReduce,initValues)
export default store;
///第2步,使用`Redux Store`,实现全局状态的多组件共享状态
import store from './Store.js'
///函数组件
const Counter = (params) => {
// state 的值 {count: 0}
const state = store.getState()
///2.1 取出默认值,设置状态
const [count, setCount] = useState(state.count)
///2.2 组件挂载后,订阅数据对象
useEffect(()=>{
const unsubscribe = store.subscribe(()=>{
//2.3 状态改变时会回调,需重新读取
setCount(store.getState().count)
})
return unsubscribe
},[state])
//2.4 返回UI的操作函数,用于发送`action`对象
const dispatch = store.dispatch
return (
<div>
<h3> 当前计数为:{count} </h3>
<button onClick={() => {
dispatch({type: 'incrementOne'})
}}>计数加1</button>
<span> </span>
<button onClick={() => {
dispatch({type: 'incrementTwo'})
}}>计数加2</button>
</div>
)
}
export default Counter
复制代码
Rudux的基本数据流:UI—>Action—>Store—>Reducer—>State—>UI
Redux ToolKit
Redux ToolKit 简称RTK是官方推荐的编写Redux状态逻辑的方式,使用它可以规避许多错误,使得使用Redux更简单,更规范。
If you are writing any Redux logic today, you should be using Redux Toolkit to write that code!
RTK 包含有助于简化许多常见用例工具,包括存储设置、创建 reducer 和编写不可变更新的逻辑,甚至一次创建整个状态切片的逻辑。
RTK的核心API:
configureStore:是对Redux的createStore()函数的友好抽象。createSlice: 该函数接收一个初始状态,一个Reducer函数对象,一个name,然后自动生成reducer函数可以响应的action,简单说action不需要手写,自动生成
RTK的安装:
# 独立安装
npm install @reduxjs/toolkit
# 创建`React`应用执行模板安装
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
复制代码
采用Redux ToolKit改造上述示例:
///第1步,创建Store对象
import { configureStore } from '@reduxjs/toolkit'
///导入函数`createStore`的参数 couterSlice
import counterSlice from './CounterSlice.js';
///创建
const store = configureStore({
reducer : counterSlice.reducer
})
export default store;
///-------CounterSlice.js-----------
import { createSlice } from "@reduxjs/toolkit";
///createSlice封装了不可变更新
const counterSlice = createSlice(
{
name: 'counter',
initialState:{
count: 0,
},
reducers:{
incrementOne: state => {state.count += 1},
incrementTwo: state => {state.count += 2},
incrementOther: (state,action) => {//action:{type: 'counter/incrementOther', payload: 5}
state.count += action.payload
}
}
}
)
export const {incrementOne,incrementTwo,incrementOther} = counterSlice.actions
export default counterSlice
///--------------CounterSlice.js-----
///第2步,组件中使用
import store from './Store.js'
import {incrementOne,incrementTwo,incrementOther} from './CounterSlice.js';
///函数组件
const Counter = (params) => {
// state 的值 {count: 0}
const state = store.getState()
///取出默认值,设置状态
const [count, setCount] = useState(state.count)
///订阅它
useEffect(()=>{
const unsubscribe = store.subscribe(()=>{
//状态改变时会回调,需重新读取
setCount(store.getState().count)
})
return unsubscribe
},[state])
//返回UI的操作函数,用于发送`action`对象
const dispatch = store.dispatch
return (
<div>
<h3> 当前计数为:{count} </h3>
<button onClick={() => dispatch(incrementOne())}>计数加1</button>
<button onClick={() => dispatch(incrementTwo())}>计数加2</button>
<button onClick={() => dispatch(incrementOther(parseInt(params.value)))}>计数加特定值</button>
</div>
)
}
export default Counter
复制代码
React-redux
React-redux是React与Redux的官方绑定库,由Redux团队维护,是独立存在的库,需要单独安装:npm install --save react-redux。
我们使用React-redux改造上述示例:
/*:
1.index.js文件中导入 Redux Store
2.使用`React-Redux`提供的`<Provider/>`全局包装`Store`----重要,不然找不到`Store`
*/
import Counter from './pages/redux/Tab3.jsx'
import { Provider } from "react-redux";
import store from "./pages/redux/Store.js";///Redux中定义的Store保持不变
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store} >
<BrowserRouter>
<Route path='Tab3' element={<Counter value ='5'/>} />
</BrowserRouter>
</Provider>
);
//3.在Tab3.jsx中导入connect函数
import { connect } from "react-redux";
//4.定义组件,参数会返回:store.dispatch & store.getState()& props
const Counter = (ownProps) => {//ownProps:{value: '5', count: 0, dispatch: ƒ}
return (
<div>
<div> 当前计数为:{ownProps.count} </div>
{/* 自己调度dispatch */}
<button onClick={() => { ownProps.dispatch({type: 'incrementOne'}) }}>计数加1</button>
<button onClick={() => { ownProps.dispatch({type: 'incrementTwo'}) }}>计数加2</button>
<button onClick={() => {
ownProps.dispatch({type: 'incrementOther',payload: parseInt(ownProps.value)})
}}>计数加特定值</button>
</div>
)
}
///从`Store`中获取组件需要的数据
const mapStateToProps = (state,ownProps) => {
//ownProps: {value: '5'}
return { count: state.count }
}
//5.连接到`ReduxStore`
const connectToStore = connect(mapStateToProps)
//6.连接到`组件`,返回容器组件
const connectToComponent = connectToStore(Counter)
export default connectToComponent
复制代码
mapStateToProps作为connect函数的参数之一,用来从Store中获取当前组件需要的数据:
- 每次存储状态更改时都会调用它。
- 它接收整个存储的状态,并应返回该组件所需的数据对象。
connect函数的另一个参数mapDispatchToProps可实现对ownProps.dispatch的封装,并最终将具体dispatch action以函数的形式绑定到组件的props。connect 提供了两种组件的数据调度的方式:
ownProps.dispatch手动调度- 创建调度函数,通过
ownProps.funcName直接使用,不需要和action交互
const mapStateToProps = (state,ownProps) => {
//ownProps: {value: '5'}
return { count: state.count }
}
const mapDispatchToProps = (dispatch,ownProps) => {
return {
incrementOne: () => dispatch({type: 'incrementOne'}),
incrementTwo: () => dispatch({type: 'incrementTwo'}),
//直接绑定到Props
incrementOther: () => dispatch({type: 'incrementOther',payload:parseInt(ownProps.value)}),
///组件内传参数
// incrementOther: (value) => dispatch({type: 'incrementOther',payload:parseInt(value)}),
}
}
const connectToStore = connect(mapStateToProps,mapDispatchToProps)
const connectToComponent = connectToStore(Counter)
export default connectToComponent
///组件使用
const Counter = (ownProps) => {
return (
<div>
<div> 当前计数为:{ownProps.count} </div>
<button onClick={ownProps.incrementOne}>计数加1</button>
<button onClick={ownProps.incrementTwo}>计数加2</button>
<button onClick={ownProps.incrementOther}>计数加特定值</button>
</div>
)
}
复制代码
React-redux hook
使用React-redux hook实现组件与React Store的交互。通过useSelector读取Store中的数据,使用useDispatch实现Action的调度。
///1.Store.js RTK
const store = configureStore({
reducer : counterSlice.reducer
})
//RTK createSlice 自动生成action: {type: 'counter/incrementOther', payload: 5}
//2.CounterSlice.js
const counterSlice = createSlice(
{
name: 'counter',
initialState:{
count: 0,
status:'idle...'
},
reducers:{
incrementOne: state => {state.count += 1},
incrementTwo: state => {state.count += 2},
incrementOther: (state,action) => {//action:{type: 'counter/incrementOther', payload: 5}
state.count += action.payload
}
}
}
)
///导出Slice的action创建函数
export const {incrementOne,incrementTwo,incrementOther} = counterSlice.actions
///3.组件使用
import { incrementOne,incrementTwo,incrementOther } from "../redux/CounterSlice.js";
import { useDispatch, useSelector } from "react-redux"
const HookCounter = (props) => {
///以下两句等价于` const {count,status} = useSelector(state => state)`
const status = useSelector(state => state.status)
const count = useSelector(state => state.count)
const dispatch = useDispatch()
return (
<div>
<div> 当前计数为:{count} </div>
<button onClick={()=>{dispatch(incrementOne())}}>计数加1</button>
<button onClick={()=>{dispatch(incrementTwo())}}>计数加2</button>
<button onClick={()=>{dispatch(incrementOther(parseInt(props.value)))}}>计数加特定值</button>
</div>
)
}
export default HookCounter
复制代码
总结:
React-Redux提供了connect函数实现了Store与组件的独立绑定,代码逻辑清晰。
React-Redux hook 提供useDispatch和useSelector访问和操作Store,代码更简洁
Redux-Thunk
Thunk这个词是一个编程术语,意思是一段执行一些延迟工作的代码。Thunk是在Redux应用程序中编写异步逻辑的标准方法,比如数据获取,也可以用作同步。
Thunk 最适合用于复杂的同步逻辑,以及简单适度的异步逻辑,例如发出标准 AJAX 请求并根据请求结果调度操作。