• 技术文章 >web前端 >前端问答

    react用什么管理状态

    青灯夜游青灯夜游2022-03-22 16:12:24原创149

    react管理状态的工具:1、利用hooks进行状态管理;2、利用Redux进行状态管理,这种方式的配套工具比较齐全,可以自定义各种中间件;3、利用Mobx进行状态管理,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。

    本教程操作环境:Windows7系统、react17.0.1版、Dell G3电脑。

    什么是 "状态"?

    jQuery 时代,JS 代码中混杂 DOM 结构,各个流程庞杂交织时,就形成面条式代码,当使用发布订阅模型时,调试会一团乱麻。

    jQuery 是针对 "过程" 的命令式编程,而那么多命令,最终都是为了更新 UI 中的 "数据",为什么不直接去改数据呢?

    北京 → 上海,把 city="北京" 变为 city="上海" 就行。不管飞机火车步行抛锚,也不管路上会不会遇到王宝强,

    现代前端框架的意义,就是问题解决思路的革新,把对 "过程" 的各种命令,变为了对 "状态" 的描述。

    什么是状态?状态就是 UI 中的动态数据。

    React 中的状态

    2013 年 5 月 React 诞生。但 2015 年之前,大概都是 jQuery 的天下。2015 年 3 月 React 0.13.0 发布,带来了 class 组件写法。

    在 React class 组件时代,状态就是 this.state,使用 this.setState 更新。

    为避免一团乱麻,React 引入了 "组件" 和 "单向数据流" 的理念。有了状态与组件,自然就有了状态在组件间的传递,一般称为 "通信"。

    父子通信较简单,而深层级、远距离组件的通信,则依赖于 "状态提升" + props 层层传递。

    于是,React 引入了 Context,一个用于解决组件 "跨级" 通信的官方方案。

    但 Context 其实相当于 "状态提升",并没有额外的性能优化,且写起来比较啰嗦。

    为优化性能,一般会添加多个 Context,写起来就更啰嗦。在项目没那么复杂时,还不如层层传递简单。

    什么是 "状态管理"?

    实用主义来说,"状态管理" 就是为了解决组件间的 "跨级" 通信。

    当然,在使用状态管理库时,其会带来一些衍生的思维模式,比如如何组织 state,如何拆分公共逻辑、业务逻辑、组件逻辑等,但归根结底,这些都不是核心缘由。

    核心就是为了解决实际问题 —— 为了通信。其它的各种概念与哲学,都不是必要的。

    Context 没那么好用,React 官方也没什么最佳实践,于是一个个社区库就诞生了。

    react中的状态管理方式

    目前比较常用的状态管理方式有hooks、redux、mobx三种,下面我将详细介绍一下这三类的使用方法以及分析各自的优缺点,以供各位进行参考。

    Hooks状态管理

    用hooks进行状态管理主要有两种方式:

    useContext+useReducer

    使用方法

    1.创建store和reducer以及全局context

    src/store/reducer.ts

    import React from "react";
    // 初始状态
    export const state = {
      count: 0,
      name: "ry",
    };
    
    // reducer 用于修改状态
    export const reducer = (state, action) => {
      const { type, payload } = action;
      switch (type) {
        case "ModifyCount":
          return {
            ...state,
            count: payload,
          };
        case "ModifyName":
          return {
            ...state,
            name: payload,
          };
        default: {
          return state;
        }
      }
    };
    
    export const GlobalContext = React.createContext(null);

    2.根组件通过 Provider 注入 context

    src/App.tsx

    import React, { useReducer } from "react";
    import './index.less'
    import { state as initState, reducer, GlobalContext} from './store/reducer'
    import Count from './components/Count'
    import Name from './components/Name'
    
    export default function () {
      const [state, dispatch] = useReducer(reducer, initState);
    
      return (
        <div>
          <GlobalContext.Provider value={{state, dispatch}}>
            <Count />
            <Name />
          </GlobalContext.Provider>
        </div>
      )
    }

    3.在组件中使用

    src/components/Count/index.tsx

    import { GlobalContext } from "@/store/reducer";
    import React, { FC, useContext } from "react";
    
    const Count: FC = () => {
      const ctx = useContext(GlobalContext)
      return (
        <div>
          <p>count:{ctx.state.count}</p>
          <button onClick={() => ctx.dispatch({ type: "ModifyCount", payload: ctx.state.count+1 })}>+1</button>
        </div>
      );
    };
    
    export default Count;

    src/components/Name/index.tsx

    import { GlobalContext } from "@/store/reducer";
    import React, { FC, useContext } from "react";
    
    const Name: FC = () => {
      const ctx = useContext(GlobalContext)
      console.log("NameRerendered")
      return (
        <div>
          <p>name:{ctx.state.name}</p>
        </div>
      );
    };
    
    export default Name;

    useState+useEffect

    使用方法

    1.创建state和reducer

    src/global-states.ts

    // 初始state
    let globalState: GlobalStates = {
      count: 0,
      name: 'ry'
    }
    
    // reducer
    export const modifyGlobalStates = (
      operation: GlobalStatesModificationType,
      payload: any
    ) => {
      switch (operation) {
        case GlobalStatesModificationType.MODIFY_COUNT:
          globalState = Object.assign({}, globalState, { count: payload })
          break
        case GlobalStatesModificationType.MODIFY_NAME:
          globalState = Object.assign({}, globalState, { name: payload })
          break
      }
      broadcast()
    }

    src/global-states.type.ts

    export interface GlobalStates {
      count: number;
      name: string;
    }
    
    export enum GlobalStatesModificationType {
      MODIFY_COUNT,
      MODIFY_NAME
    }

    2.写一个发布订阅模式,让组件订阅globalState

    src/global-states.ts

    import { useState, useEffect } from 'react'
    import {
      GlobalStates,
      GlobalStatesModificationType
    } from './global-states.type'
    
    let listeners = []
    
    let globalState: GlobalStates = {
      count: 0,
      name: 'ry'
    }
    // 发布,所有订阅者收到消息,执行setState重新渲染
    const broadcast = () => {
      listeners.forEach((listener) => {
        listener(globalState)
      })
    }
    
    export const modifyGlobalStates = (
      operation: GlobalStatesModificationType,
      payload: any
    ) => {
      switch (operation) {
        case GlobalStatesModificationType.MODIFY_COUNT:
          globalState = Object.assign({}, globalState, { count: payload })
          break
        case GlobalStatesModificationType.MODIFY_NAME:
          globalState = Object.assign({}, globalState, { name: payload })
          break
      }
      // 状态改变即发布
      broadcast()
    }
    
    // useEffect + useState实现发布订阅
    export const useGlobalStates = () => {
      const [value, newListener] = useState(globalState)
    
      useEffect(() => {
        // newListener是新的订阅者
        listeners.push(newListener)
        // 组件卸载取消订阅
        return () => {
          listeners = listeners.filter((listener) => listener !== newListener)
        }
      })
    
      return value
    }

    3.组件中使用

    src/App.tsx

    import React from 'react'
    import './index.less'
    import Count from './components/Count'
    import Name from './components/Name'
    
    export default function () {
      return (
        <div>
          <Count />
          <Name />
        </div>
      )
    }

    src/components/Count/index.tsx

    import React, { FC } from 'react'
    import { useGlobalStates, modifyGlobalStates } from '@/store/global-states'
    import { GlobalStatesModificationType } from '@/store/global-states.type'
    
    const Count: FC = () => {
      // 调用useGlobalStates()即订阅globalStates()
      const { count } = useGlobalStates()
      return (
        <div>
          <p>count:{count}</p>
          <button
            onClick={() =>
              modifyGlobalStates(
                GlobalStatesModificationType.MODIFY_COUNT,
                count + 1
              )
            }
          >
            +1
          </button>
        </div>
      )
    }
    
    export default Count

    src/components/Name/index.tsx

    import React, { FC } from 'react'
    import { useGlobalStates } from '@/store/global-states'
    
    const Count: FC = () => {
      const { name } = useGlobalStates()
      console.log('NameRerendered')
      return (
        <div>
          <p>name:{name}</p>
        </div>
      )
    }
    
    export default Count

    优缺点分析

    由于以上两种都是采用hooks进行状态管理,这里统一进行分析

    优点

    缺点

    Redux状态管理

    使用方法:

    1.引入redux

    yarn add redux react-redux @types/react-redux redux-thunk

    2.新建reducer

    在src/store/reducers文件夹下新建addReducer.ts(可建立多个reducer)

    import * as types from '../action.types'
    import { AnyAction } from 'redux'
    
    // 定义参数接口
    export interface AddState {
      count: number
      name: string
    }
    
    // 初始化state
    let initialState: AddState = {
      count: 0,
      name: 'ry'
    }
    
    // 返回一个reducer
    export default (state: AddState = initialState, action: AnyAction): AddState => {
      switch (action.type) {
        case types.ADD:
          return { ...state, count: state.count + action.payload }
        default:
          return state
      }
    }

    在src/stores文件夹下新建action.types.ts
    主要用于声明action类型

    export const ADD = 'ADD'
    export const DELETE = 'DELETE'

    3.合并reducer

    在src/store/reducers文件夹下新建index.ts

    import { combineReducers, ReducersMapObject, AnyAction, Reducer } from 'redux'
    import addReducer, { AddState } from './addReducer'
    
    // 如有多个reducer则合并reducers,模块化
    export interface CombinedState {
      addReducer: AddState
    }
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      addReducer
    }
    const reducer: Reducer<CombinedState, AnyAction> = combineReducers(reducers)
    
    export default reducer

    3.创建store

    在src/stores文件夹下新建index.ts

    import {
      createStore,
      applyMiddleware,
      StoreEnhancer,
      StoreEnhancerStoreCreator,
      Store
    } from 'redux'
    import thunk from 'redux-thunk'
    import reducer from './reducers'
    
    // 生成store增强器
    const storeEnhancer: StoreEnhancer = applyMiddleware(thunk)
    const storeEnhancerStoreCreator: StoreEnhancerStoreCreator = storeEnhancer(createStore)
    
    const store: Store = storeEnhancerStoreCreator(reducer)
    
    export default store

    4.根组件通过 Provider 注入 store

    src/index.tsx(用provider将App.tsx包起来)

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import { Provider } from 'react-redux'
    import store from './store'
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    )

    5.在组件中使用

    src/somponents/Count/index.tsx

    import React, { FC } from 'react'
    import { connect } from 'react-redux'
    import { Dispatch } from 'redux'
    import { AddState } from 'src/store/reducers/addReducer'
    import { CombinedState } from 'src/store/reducers'
    import * as types from '@/store/action.types'
    
    // 声明参数接口
    interface Props {
      count: number
      add: (num: number) => void
    }
    
    // ReturnType获取函数返回值类型,&交叉类型(用于多类型合并)
    // type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>
    
    const Count: FC<Props> = (props) => {
      const { count, add } = props
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={() => add(5)}>addCount</button>
        </div>
      )
    }
    
    // 这里相当于自己手动做了映射,只有这里映射到的属性变化,组件才会rerender
    const mapStateToProps = (state: CombinedState) => ({
      count: state.addReducer.count
    })
    
    const mapDispatchToProps = (dispatch: Dispatch) => {
      return {
        add(num: number = 1) {
          // payload为参数
          dispatch({ type: types.ADD, payload: num })
        }
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Count)

    src/somponents/Name/index.tsx

    import React, { FC } from 'react'
    import { connect } from 'react-redux'
    import { Dispatch } from 'redux'
    import { AddState } from 'src/store/reducers/addReducer'
    import { CombinedState } from 'src/store/reducers'
    import * as types from '@/store/action.types'
    
    // 声明参数接口
    interface Props {
      name: string
    }
    
    const Name: FC<Props> = (props) => {
      const { name } = props
      console.log('NameRerendered')
      return (
        <div>
          <p>name: {name}</p>
        </div>
      )
    }
    
    // name变化组件才会rerender
    const mapStateToProps = (state: CombinedState) => ({
      name: state.addReducer.name
    })
    
    // addReducer内任意属性变化组件都会rerender
    // const mapStateToProps = (state: CombinedState) => state.addReducer
    
    export default connect(mapStateToProps)(Name)

    优缺点分析

    优点

    缺点

    Mobx状态管理

    MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。

    常规使用(mobx-react)

    使用方法

    1.引入mobx

    yarn add mobx mobx-react -D

    2.创建store

    在/src/store目录下创建你要用到的store(在这里使用多个store进行演示)
    例如:
    store1.ts

    import { observable, action, makeObservable } from 'mobx'
    
    class Store1 {
      constructor() {
        makeObservable(this) //mobx6.0之后必须要加上这一句
      }
      @observable
      count = 0
    
      @observable
      name = 'ry'
    
      @action
      addCount = () => {
        this.count += 1
      }
    }
    
    const store1 = new Store1()
    export default store1

    store2.ts
    这里使用 makeAutoObservable代替了makeObservable,这样就不用对每个state和action进行修饰了(两个方法都可,自行选择)

    import { makeAutoObservable } from 'mobx'
    
    class Store2 {
      constructor() {
        // mobx6.0之后必须要加上这一句
        makeAutoObservable(this)
      }
      time = 11111111110
    }
    
    const store2 = new Store2()
    export default store2

    3.导出store

    src/store/index.ts

    import store1 from './store1'
    import store2 from './store2'
    
    export const store = { store1, store2 }

    4.根组件通过 Provider 注入 store

    src/index.tsx(用provider将App.tsx包起来)

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './store'
    import { Provider } from 'mobx-react'
    
    ReactDOM.render(
      <Provider {...store}>
        <App />
      </Provider>,
      document.getElementById('root')
    )

    5.在组件中使用

    src/somponents/Count/index.tsx

    import React, { FC } from 'react'
    import { observer, inject } from 'mobx-react'
    
    // 类组件用装饰器注入,方法如下
    // @inject('store1')
    // @observer
    interface Props {
      store1?: any
    }
    const Count: FC<Props> = (props) => {
      const { count, addCount } = props.store1
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={addCount}>addCount</button>
        </div>
      )
    }
    // 函数组件用Hoc,方法如下(本文统一使用函数组件)
    export default inject('store1')(observer(Count))

    src/components/Name/index.tsx

    import React, { FC } from 'react'
    import { observer, inject } from 'mobx-react'
    
    interface Props {
      store1?: any
    }
    
    const Name: FC<Props> = (props) => {
      const { name } = props.store1
      console.log('NameRerendered')
      return (
        <div>
          <p>name: {name}</p>
        </div>
      )
    }
    // 函数组件用Hoc,方法如下(本文统一使用函数组件)
    export default inject('store1')(observer(Name))

    优缺点分析:

    优点:

    缺点:

    最佳实践(mobx+hooks)

    使用方法

    1.引入mobx

    同上

    2.创建store

    同上

    3.导出store(结合useContext)

    src/store/index.ts

    import React from 'react'
    import store1 from './store1'
    import store2 from './store2'
    
    // 导出store1
    export const storeContext1 = React.createContext(store1)
    export const useStore1 = () => React.useContext(storeContext1)
    
    // 导出store2
    export const storeContext2 = React.createContext(store2)
    export const useStore2 = () => React.useContext(storeContext2)

    4.在组件中使用

    无需使用Provider注入根组件
    src/somponents/Count/index.tsx

    import React, { FC } from 'react'
    import { observer } from 'mobx-react'
    import { useStore1 } from '@/store/'
    
    // 类组件可用装饰器,方法如下
    // @observer
    
    const Count: FC = () => {
      const { count, addCount } = useStore1()
      return (
        <div>
          <p>count: {count}</p>
          <button onClick={addCount}>addCount</button>
        </div>
      )
    }
    // 函数组件用Hoc,方法如下(本文统一使用函数组件)
    export default observer(Count)

    src/components/Name/index.tsx

    import React, { FC } from 'react'
    import { observer } from 'mobx-react'
    import { useStore1 } from '@/store/'
    
    const Name: FC = () => {
      const { name } = useStore1()
      console.log('NameRerendered')
      return (
        <div>
          <p>name: {name}</p>
        </div>
      )
    }
    
    export default observer(Name)

    优缺点分析:

    优点:

    缺点:

    Mobx自动订阅实现原理

    基本概念

    Observable  //被观察者,状态
    Observer    //观察者,组件
    Reaction    //响应,是一类的特殊的 Derivation,可以注册响应函数,使之在条件满足时自动执行。

    建立依赖

    我们给组件包的一层observer实现了这个功能

    export default observer(Name)

    组件每次mount和update时都会执行一遍useObserver函数,useObserver函数中通过reaction.track进行依赖收集,将该组件加到该Observable变量的依赖中(bindDependencies)。

    // fn = function () { return baseComponent(props, ref); 
    export function useObserver(fn, baseComponentName) {
        ...
        var rendering;
        var exception;
        reaction.track(function () {
            try {
                rendering = fn();
            }
            catch (e) {
                exception = e;
            }
        });
        if (exception) {
            throw exception; // re-throw any exceptions caught during rendering
        }
        return rendering;
    }

    reaction.track()

     _proto.track = function track(fn) {
        // 开始收集
        startBatch();
        var result = trackDerivedFunction(this, fn, undefined);
        // 结束收集
        endBatch();
      };

    reaction.track里面的核心内容是trackDerivedFunction

    function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
       	...
        let result
    
        // 执行回调f,触发了变量(即组件的参数)的 get,从而获取 dep【收集依赖】
        if (globalState.disableErrorBoundaries === true) {
            result = f.call(context)
        } else {
            try {
                result = f.call(context)
            } catch (e) {
                result = new CaughtException(e)
            }
        }
        globalState.trackingDerivation = prevTracking
    
        // 给 observable 绑定 derivation
        bindDependencies(derivation)
       ...
        return result
    }

    触发依赖

    Observable(被观察者,状态)修改后,会调用它的set方法,然后再依次执行该Observable之前收集的依赖函数,触发rerender。

    组件更新

    用组件更新来简单阐述总结一下:mobx的执行原理。

    每次都进行依赖收集的原因是,每次执行依赖可能会发生变化。

    总结

    简单总结了一下目前较为常用的状态管理方式,我个人最喜欢的使用方式是Mobx+Hooks,简单轻量易上手。各位可以根据自己的需求选择适合自己项目的管理方式。

    【相关推荐:Redis视频教程

    以上就是react用什么管理状态的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:react 管理状态
    上一篇:jquery中live的用法是什么 下一篇:jquery怎么去掉某个字符串
    Web大前端开发直播班

    相关文章推荐

    • hooks怎么样,为什么vue和react都选择它!• 怎么配置VSCode,苏爽的调试Vue、React 代码!• VSCode插件分享:一个实时预览Vue/React组件的插件• 什么是hook?聊聊React中常用的两个Hook• 带你了解React中的Ref,值得了解的知识点分享• react hook和class的区别有哪些

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网