中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

react怎么管理狀態

發布時間:2022-03-22 16:34:35 來源:億速云 閱讀:1960 作者:iii 欄目:web開發

這篇文章主要介紹“react怎么管理狀態”,在日常操作中,相信很多人在react怎么管理狀態問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”react怎么管理狀態”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

react管理狀態的工具:1、利用hooks進行狀態管理;2、利用Redux進行狀態管理,這種方式的配套工具比較齊全,可以自定義各種中間件;3、利用Mobx進行狀態管理,它通過透明的函數響應式編程使得狀態管理變得簡單和可擴展。

react怎么管理狀態

本教程操作環境: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

  • useState+useEffect

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進行狀態管理,這里統一進行分析

優點

  • 代碼比較簡潔,如果你的項目比較簡單,只有少部分狀態需要提升到全局,大部分組件依舊通過本地狀態來進行管理。這時,使用 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 '../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)

優缺點分析

優點

  • 組件會訂閱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 '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))

優缺點分析:

優點:

  • 組件會自動訂閱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 '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)

優缺點分析:

優點:
  • 學習成本少,基礎知識非常簡單,跟 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去更新組件,然后結束依賴收集。

每次都進行依賴收集的原因是,每次執行依賴可能會發生變化。

到此,關于“react怎么管理狀態”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

三门县| 广昌县| 古交市| 青神县| 中宁县| 苍溪县| 南漳县| 蒙阴县| 萝北县| 永寿县| 监利县| 全椒县| 阳高县| 许昌县| 南江县| 固安县| 松阳县| 通山县| 宣汉县| 大足县| 云林县| 司法| 太原市| 介休市| 中宁县| 陆河县| 留坝县| 和硕县| 兰州市| 青龙| 阜阳市| 顺义区| 普安县| 二连浩特市| 安康市| 海盐县| 清苑县| 安乡县| 宁陕县| 祁门县| 马鞍山市|