React状态管理 | 青训营笔记

104 阅读8分钟

React状态管理简介

什么是状态管理

从React诞生之后,前端组件化的方案深入人心,React遵循的是单向数据流的原则,属性通过Props自上而下的传递。当页面的比较简单,组件之间的层级关系比较浅时,这种自上而下的单向数据流的方式是不会有问题的。如果页面一复杂,组件的嵌套层级一深,这种单向数据流的传递方式,将会使你陷入到“ 嵌套地狱”。状态管理本身,解决的就是这种“嵌套"地狱的问题,解决的是跨层级组件之间的数据通信和状态共享。

状态管理的本质

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

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

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

为什么React有这么多状态管理工具?

Vue: Vuex(Pinia)
Angular: Service和Rxjs
React: Flux、Redux、Mobx、Rxjs、Recoil、Jotai、Zustand
跟不同前端框架的定义有关,Vue和Angular双向数据绑定,计算属性等,数据是响应式的,控制视图刷新,拥有计算属性等,这些使得Vue和Angular需要状态管理的场景减少,此外其本身就包含了完整的状态管理工具,比如Vue的Vuex和Pinia,Angular的Service(RXjs)等,从官方定调。而React不一样,React是一个纯U层的前端框架,Ul = fn(state),React将状态的变动完全交给开发者。

1. Local State(props)
local State顾名思义,就是组件级别的局部状态,比如:

import {useState} from 'react' 
const Hello = () =>{
  const [name ,setName] = useState( ' Jony ' )
  return <>Hello,{name}</>
}

上述的name就是一个最简单的局部local State。只在Hello这个组件中生效,当组件创建时初始化和生效,组件销毁时失效。
当然这种向上延伸的方法,不是无限的,如果一直往上延伸,会出现一个父组件嵌套10几层子组件的情况,必须要有一个“度”,超过这个“度”后,我们就认为local State的方式就不太实用了。 这个“度”,在前端开发中,大部分情况下我们认为就是子页面。我们一般认为,单页应用中,子页面以及子页面之下的组件都是可以用local State来解决状态管理问题的,而子页面和子页面之间,是不需要再往上延伸。那么子页面和子页面之间如何通信呢?
答案是:子页面之间的通信,React本身提供了Context。

2.Context
React中的Context解决了react中,props或者state进行多级数据传递,则数据需要自顶下流经过每一级组件,无法跨级的问题。但是Context在页面间共享数据的时候同样有很多问题:

1.Context相当于全局变量,难以追溯数据的变更情况
2.Context的组件内部耦合度太高,不利于组件的复用和单元测试
3.会产生不必要的更新(比如会穿透memo和dependicies等)
4.Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合。
5.粒度也不太好控制,不能细粒度的区分组件依赖了哪一个Context 6.多个Context会存在层层嵌套的问题

3. Redux
Redux是从Flux演变而来的,Flux它是Facebook官方给出的应用架构,利用数据的单向流动的形式对公共状态进行管理,不过现在已经被淘汰了,不过其设计思想还是可以参考和借鉴的。 ALE%KA9F5@EO$WCALDYL.png

因为Redux的上述特性,使得Redux可以做时间旅行。时间旅行︰顾名思义,就是可以随时穿越到以前和未来,让应用程序切换到任意时间的状态。因此,如果复杂的场景,特别是存在页面组件间复杂的通信的场景非常适合用Redux来管理状态。 Redux比较适合用于大型Web项目,尤其是一些交互足够复杂、组件通信频繁的场景,状态可预测和回溯是非常有价值的。还有一种场景,比如需要事故重现,这种定义和上报事故异常和重现的场景,Redux也很有意义。 Redux的缺点也很明显,首先为了实现纯函数的Reducer,Redux必须处理各种各样的副作用,需要引入一系列的副作用中间件,加重的心智负担,此外Action,Dispatch,Reducer的模式需要写过多的样版代码,虽然通过React hooks.和Redux toolkit可以减少一定的样板代码,但是复杂度还是摆在哪里。因此中小项目,也不太推荐使用Redux,可能Context或者React hooks中的useReducer就能满足你的需求。

Redux架构图: image.png

4.Mobox
它通过透明的函数响应式编程使得状态管理变得简单和可扩展,Mobx跟Vue的设计比较相似,是一个响应式的状态管理库。Mobx借助于装饰器的实现,使得代码更加简洁易懂。由于使用了可观察对象,所以Mobx可以做到直接修改状态,而不必像Redux一样编写繁琐的actions和reducers。 Mobx的优势在于上手简单,可以直接修改状态,不需要编写繁琐的Action和Reducer,也不需要引入各 种 复杂的中间件,局部精确更新,免去了粒度控制烦恼,自始至终一份引用,不需要immutable,也没有复制对象 的额外开销。因此前端数据流不太复杂的情况,使用Mobx,因为更加清晰,也便于维护。但是正是因为Mobx 的灵活,Mobx的代码风格很难统一。 不过Mobx是不能实现时间旅行和回溯的,因此不太适合前端数据流比较复杂的场景,此外,随着React hooks,比如useReducer等的,以及React自身的原子型状态管理工具Recoil。Mobx的使用场景会被进一步压缩,目前的项目中使用Mobx的场景已经越来越小。

5.Recoil
Recoil是React官方内置的状态管理工具,一定程度上解决了Local State和Context的局限性,且能够兼容 React的新特性比如Concurrent模式等。
解决的问题:

  1. 组件间的状态共享只能通过将state提升至它们的公共祖先来实现,但这样做可能导致重新渲染一颗巨大组件树。
  2. context只能存储单一值,无法存储多个各自拥有消费者的值的集合。

6.Zustand Zustand是主打轻量级的状态管理工具,没有Redux那样臃肿的设计,也没有兼容React类组件的历史包袱,Zustand状态管理工具体积很小,因此很适合移动端的网页。

React状态管理工具可以分为以下几类:

React自带: Local State(props)和Context
单向数据流: Flux、Redux(Redux-toolkit)
双向数据绑定: Mobx
原子型状态管理: Recoil、Jotai
异步操作密集型: Rxjs

发布/订阅模式

CSSE70C0KC19.png

Context

案例目录png

//index.tsx
import A from './components/A'
function App() {
  return (
      <A/>
  );
}

export default App;
//Context.ts

import { createContext } from "react";

const MyContext = createContext('');

export {MyContext};
//A.tsx
import { MyContext } from '../../Context/context'
import B from '../B/index'

export default function index() {
  return (
    <div>
      <div>我是A组件:</div>
      <MyContext.Provider value={'Context共享值'}>
        <B />
      </MyContext.Provider>
    </div>
  )
}
//B.tsx
import C from '../C/index'

export default function index() {
    return (
        <div>
            <div>我是B组件:</div>
            <C />
        </div>
    )
}
//C.tsx
import { MyContext } from '../../Context/context'

export default function index() {
    return (
        <MyContext.Consumer>
            {
                value => (
                    <div>我是C组件:{value}</div>
                )
            }
        </MyContext.Consumer>
    )
}

Redux

案例目录.png

//index.tsx
import {useEffect, useRef, useState} from "react";
import store from "../../redux/store";
import {createIncrementAction} from '../../redux/count_action'

function App() {
  const [,setUpdate] :any = useState();
  const sel:any = useRef(null);
  useEffect(() => {
    store.subscribe(() => {
      setUpdate({});
    })
  })
  const add = () => {
    store.dispatch(createIncrementAction(Number(sel.current.value)));
    
  }
  const cut = () => {
    store.dispatch({type:'decrement',data:Number(sel.current.value)});
  }
  return (
    <div style={{marginTop: '10px'}}>
        <span style={{marginRight: '10px'}}>数字:{store.getState()}</span>
        <select ref={sel} name="" id="">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <button onClick={add}>+</button>
        <button onClick={cut}>-</button>
    </div>
  );
}

export default App;

//store.ts
import { legacy_createStore as createStore } from "redux";
import countReducer from './count_reducer'

export default createStore(countReducer)
//count_action.ts
export const createIncrementAction = (data: any) => ({type:'increment',data})
//count_reducer.ts
const initState = 0;
function countReducer(preState = initState, action: { type: any; data: any; }) {
    const { type, data } = action;
    switch (type) {
        case 'increment':
            return preState + data
        case 'decrement':
            return preState - data
        default:
            return preState
    }
}

export default countReducer;

Redux三大原则

  1. 单一数据源:
    在redux中,整个应用的全局State(再次注意是全局state),都会保存在一个store中,一个单一数据源state tree也简化了应用的调试和和监控;它也让你在开发中能将应用数据持久化到本地,从而加速开反同用。此外,有一些功能以前很难实现,比如“撤销/重做”,在单一数据源的原则下,使用Redux实现将非常容易。
  2. Store中的State是只读的:
    我们不能直接修改store中的state,store中的state是只读的。唯一能改变store中的state的方式就是通过action
  3. 使用纯函数来执行修改: 接受纯函数来接受aciton,该纯函数叫reducer,可以改变store中的state.