REACT状态管理

124 阅读8分钟

什么是状态管理

React状态管理简介

什么是状态管理?

状态管理工具的本质:管理共享内存中的状态

  1. 共享内存
  2. 管理状态
  3. 页面通信
  4. 组件通信
  5. 刷新失效?

详细定义: 单页应用的各个组件本身是共享内存的,如果将状态保存在内存中 就可以读写统一内存中的变量,从而达到状态共享的目的。

为什么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在页面间共享数据的时候同样有很多问题。

  1. Context相当于全局变量, 难以追溯数据的变更情况
  2. 使用Context的组件内部耦合度太高,不利于组件的复用和单元测试
  3. 会产生不必要的更新(比如会穿透memo和dependicies等
  4. Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合
  5. 粒度也不太好控制,不能细粒度的区分组件依赖了哪一个
  6. 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:是对 ReduxcreateStore() 函数的友好抽象。
  • 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-reduxReactRedux的官方绑定库,由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以函数的形式绑定到组件的propsconnect 提供了两种组件的数据调度的方式:

  1. ownProps.dispatch手动调度
  2. 创建调度函数,通过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 提供useDispatchuseSelector访问和操作Store,代码更简洁

Redux-Thunk

Thunk这个词是一个编程术语,意思是一段执行一些延迟工作的代码。Thunk是在Redux应用程序中编写异步逻辑的标准方法,比如数据获取,也可以用作同步。

Thunk 最适合用于复杂的同步逻辑,以及简单适度的异步逻辑,例如发出标准 AJAX 请求并根据请求结果调度操作。