React は状態を管理するために何を使用しますか?

青灯夜游
リリース: 2022-03-22 16:12:24
オリジナル
2830 人が閲覧しました

React 状態管理ツール: 1. 状態管理にフックを使用する; 2. 状態管理に Redux を使用する この方法には比較的完全なサポート ツール セットがあり、さまざまなミドルウェアをカスタマイズできます; 3. 状態管理に Mobx を使用します状態管理: 透過的な関数型リアクティブ プログラミングを通じて状態管理をシンプルかつスケーラブルにします。

React は状態を管理するために何を使用しますか?

このチュートリアルの動作環境: Windows7 システム、react17.0.1 バージョン、Dell G3 コンピューター。

「ステータス」とは何ですか?

jQuery時代になると、JSコードの中にDOM構造が混在し、様々な処理が複雑に絡み合ってスパゲティコードが形成され、パブリッシュ・サブスクライブモデルを使用するとデバッグがめちゃくちゃになってしまいます。

jQuery は「プロセス」のための命令型プログラミングであり、非常に多くのコマンドは最終的に UI 内の「データ」を更新することを目的としています。なぜデータを直接変更しないのでしょうか?

北京 → 上海の場合は、city="Beijing" を city="Shanghai" に変更するだけです。飛行機や電車が故障しようと、徒歩で王宝強に会おうが、

最新のフロントエンド フレームワークの重要性は、問題解決のアイデアの革新であり、さまざまなコマンドを実行します。 「プロセス」から「状態」へ」の説明。

ステータスとは何ですか? State は UI 内の動的データです。

React の状態

2013 年 5 月 React が誕生しました。しかし、2015 年以前は、おそらく jQuery が世界だったでしょう。 React 0.13.0 は 2015 年 3 月にリリースされ、クラス コンポーネントの記述方法が導入されました。

React クラス コンポーネントの時代では、状態は this.state であり、this.setState を使用して更新されます。

混乱を避けるために、React は「コンポーネント」と「一方向データ フロー」の概念を導入しました。状態とコンポーネントでは、コンポーネント間の状態の転送が当然発生します。これは一般に「通信」と呼ばれます。

親子通信は比較的単純ですが、深層コンポーネントと長距離コンポーネント間の通信は、「ステータス プロモーション」プロパティのレイヤーごとの転送に依存します。

そこで、React は、コンポーネント間の「クロスレベル」通信のための公式ソリューションである Context を導入しました。

しかし、コンテキストは実際には「ステータスの改善」と同等であり、追加のパフォーマンスの最適化はなく、記述はより冗長です。

パフォーマンスを最適化するために、通常は複数のコンテキストが追加されるため、記述がより冗長になります。プロジェクトがそれほど複雑でない場合、レイヤーを通過させるほど単純ではありません。

「状態管理」とは何ですか?

実際的に言えば、「状態管理」はコンポーネント間の「クロスレベル」通信を解決することです。

もちろん、状態管理ライブラリを使用すると、状態を整理する方法、パブリック ロジック、ビジネス ロジック、コンポーネント ロジックを分割する方法など、いくつかの派生的な思考モードがもたらされますが、最終的には分析すると、これらは主な理由ではありません。

核心は、コミュニケーションに関する実際的な問題を解決することです。他のさまざまな概念や哲学は必要ありません。

Context はそれほど使いやすいものではなく、React には公式のベスト プラクティスがないため、コミュニティ ライブラリが次々に生まれました。

react における状態管理方法

現在よく使われている状態管理方法としては、hooks、redux、mobx の 3 つが挙げられますが、以下ではこれら 3 つの使い方を詳しく紹介し、それぞれの特徴を分析していきます。それぞれのメリット・デメリットは参考までに。

フックの状態管理

状態管理にフックを使用するには、主に 2 つの方法があります:

  • useContext useReducer
  • useState useEffect

# useContext useReducer

使用方法

1.ストア、リデューサー、グローバルコンテキストの作成

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

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. 状態とレデューサを作成します #src/global-states.ts

// 初始state
let globalState: GlobalStates = {
  count: 0,
  name: &#39;ry&#39;
}

// 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 &#39;react&#39;
import {
  GlobalStates,
  GlobalStatesModificationType
} from &#39;./global-states.type&#39;

let listeners = []

let globalState: GlobalStates = {
  count: 0,
  name: &#39;ry&#39;
}
// 发布,所有订阅者收到消息,执行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 &#39;react&#39;
import &#39;./index.less&#39;
import Count from &#39;./components/Count&#39;
import Name from &#39;./components/Name&#39;

export default function () {
  return (
    <div>
      <Count />
      <Name />
    </div>
  )
}
ログイン後にコピー

src/components/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { useGlobalStates, modifyGlobalStates } from &#39;@/store/global-states&#39;
import { GlobalStatesModificationType } from &#39;@/store/global-states.type&#39;

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 &#39;react&#39;
import { useGlobalStates } from &#39;@/store/global-states&#39;

const Count: FC = () => {
  const { name } = useGlobalStates()
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name:{name}</p>
    </div>
  )
}

export default Count
ログイン後にコピー

优缺点分析

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

优点

  • 代码比较简洁,如果你的项目比较简单,只有少部分状态需要提升到全局,大部分组件依旧通过本地状态来进行管理。这时,使用 hookst进行状态管理就挺不错的。杀鸡焉用牛刀。

缺点

  • 两种hooks管理方式都有一个很明显的缺点,会产生大量的无效rerender,如上例中的Count和Name组件,当state.count改变后,Name组件也会rerender,尽管他没有使用到state.count。这在大型项目中无疑是效率比较低的。

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 &#39;../action.types&#39;
import { AnyAction } from &#39;redux&#39;

// 定义参数接口
export interface AddState {
  count: number
  name: string
}

// 初始化state
let initialState: AddState = {
  count: 0,
  name: &#39;ry&#39;
}

// 返回一个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 = &#39;ADD&#39;
export const DELETE = &#39;DELETE&#39;
ログイン後にコピー

3.合并reducer

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

import { combineReducers, ReducersMapObject, AnyAction, Reducer } from &#39;redux&#39;
import addReducer, { AddState } from &#39;./addReducer&#39;

// 如有多个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 &#39;redux&#39;
import thunk from &#39;redux-thunk&#39;
import reducer from &#39;./reducers&#39;

// 生成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 &#39;react&#39;
import ReactDOM from &#39;react-dom&#39;
import App from &#39;./App&#39;
import { Provider } from &#39;react-redux&#39;
import store from &#39;./store&#39;

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById(&#39;root&#39;)
)
ログイン後にコピー

5.在组件中使用

src/somponents/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { connect } from &#39;react-redux&#39;
import { Dispatch } from &#39;redux&#39;
import { AddState } from &#39;src/store/reducers/addReducer&#39;
import { CombinedState } from &#39;src/store/reducers&#39;
import * as types from &#39;@/store/action.types&#39;

// 声明参数接口
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 &#39;react&#39;
import { connect } from &#39;react-redux&#39;
import { Dispatch } from &#39;redux&#39;
import { AddState } from &#39;src/store/reducers/addReducer&#39;
import { CombinedState } from &#39;src/store/reducers&#39;
import * as types from &#39;@/store/action.types&#39;

// 声明参数接口
interface Props {
  name: string
}

const Name: FC<Props> = (props) => {
  const { name } = props
  console.log(&#39;NameRerendered&#39;)
  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)
ログイン後にコピー

优缺点分析

优点

  • 组件会订阅store中具体的某个属性【mapStateToProps手动完成】,只要当属性变化时,组件才会rerender,渲染效率较高
  • 流程规范,按照官方推荐的规范和结合团队风格打造一套属于自己的流程。
  • 配套工具比较齐全redux-thunk支持异步,redux-devtools支持调试
  • 可以自定义各种中间件

缺点

  • state+action+reducer的方式不太好理解,不太直观
  • 非常啰嗦,为了一个功能又要写reducer又要写action,还要写一个文件定义actionType,显得很麻烦
  • 使用体感非常差,每个用到全局状态的组件都得写一个mapStateToProps和mapDispatchToProps,然后用connect包一层,我就简单用个状态而已,咋就这么复杂呢
  • 当然还有一堆的引入文件,100行的代码用了redux可以变成120行,不过换个角度来说这也算增加了自己的代码量
  • 好像除了复杂也没什么缺点了

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 &#39;mobx&#39;

class Store1 {
  constructor() {
    makeObservable(this) //mobx6.0之后必须要加上这一句
  }
  @observable
  count = 0

  @observable
  name = &#39;ry&#39;

  @action
  addCount = () => {
    this.count += 1
  }
}

const store1 = new Store1()
export default store1
ログイン後にコピー

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

import { makeAutoObservable } from &#39;mobx&#39;

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 &#39;./store1&#39;
import store2 from &#39;./store2&#39;

export const store = { store1, store2 }
ログイン後にコピー

4.根组件通过 Provider 注入 store

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

import React from &#39;react&#39;
import ReactDOM from &#39;react-dom&#39;
import App from &#39;./App&#39;
import store from &#39;./store&#39;
import { Provider } from &#39;mobx-react&#39;

ReactDOM.render(
  <Provider {...store}>
    <App />
  </Provider>,
  document.getElementById(&#39;root&#39;)
)
ログイン後にコピー

5.在组件中使用

src/somponents/Count/index.tsx

import React, { FC } from &#39;react&#39;
import { observer, inject } from &#39;mobx-react&#39;

// 类组件用装饰器注入,方法如下
// @inject(&#39;store1&#39;)
// @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(&#39;store1&#39;)(observer(Count))
ログイン後にコピー

src/components/Name/index.tsx

import React, { FC } from &#39;react&#39;
import { observer, inject } from &#39;mobx-react&#39;

interface Props {
  store1?: any
}

const Name: FC<Props> = (props) => {
  const { name } = props.store1
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name: {name}</p>
    </div>
  )
}
// 函数组件用Hoc,方法如下(本文统一使用函数组件)
export default inject(&#39;store1&#39;)(observer(Name))
ログイン後にコピー

优缺点分析:

优点:

  • 组件会自动订阅store中具体的某个属性,无需手动订阅噢!【下文会简单介绍下原理】只有当订阅的属性变化时,组件才会rerender,渲染效率较高
  • 一个store即写state,也写action,这种方式便于理解,并且代码量也会少一些

缺点:

  • 当我们选择的技术栈是React+Typescript+Mobx时,这种使用方式有一个非常明显的缺点,引入的store必须要在props的type或interface定义过后才能使用(会增加不少代码量),而且还必须指定这个store为可选的,否则会报错(因为父组件其实没有传递这个prop给子组件),这样做还可能会致使对store取值时,提示可能为undefined,虽然能够用“!”排除undefined,可是这种作法并不优雅。

最佳实践(mobx+hooks)

使用方法

1.引入mobx

同上

2.创建store

同上

3.导出store(结合useContext)

src/store/index.ts

import React from &#39;react&#39;
import store1 from &#39;./store1&#39;
import store2 from &#39;./store2&#39;

// 导出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 &#39;react&#39;
import { observer } from &#39;mobx-react&#39;
import { useStore1 } from &#39;@/store/&#39;

// 类组件可用装饰器,方法如下
// @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 &#39;react&#39;
import { observer } from &#39;mobx-react&#39;
import { useStore1 } from &#39;@/store/&#39;

const Name: FC = () => {
  const { name } = useStore1()
  console.log(&#39;NameRerendered&#39;)
  return (
    <div>
      <p>name: {name}</p>
    </div>
  )
}

export default observer(Name)
ログイン後にコピー

优缺点分析:

优点:

  • 学习成本少,基础知识非常简单,跟 Vue 一样的核心原理,响应式编程。
  • 一个store即写state,也写action,这种方式便于理解
  • 组件会自动订阅store中具体的某个属性,只要当属性变化时,组件才会rerender,渲染效率较高
  • 成功避免了上一种使用方式的缺点,不用对使用的store进行interface或type声明!
  • 内置异步action操作方式
  • 代码量真的很少,使用很简单有没有,强烈推荐!

缺点:

  • 过于自由:Mobx提供的约定及模版代码很少,这导致开发代码编写很自由,如果不做一些约定,比较容易导致团队代码风格不统一,团队建议启用严格模式!
  • 使用方式过于简单

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的执行原理。

  • observer这个装饰器(也可以是Hoc),对React组件的render方法进行track。

  • 将render方法,加入到各个observable的依赖中。当observable发生变化,track方法就会执行。

  • track中,还是先进行依赖收集,调用forceUpdate去更新组件,然后结束依赖收集。

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

总结

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

【相关推荐:Redis视频教程

以上がReact は状態を管理するために何を使用しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!