這篇文章主要介紹“react怎么管理狀態(tài)”,在日常操作中,相信很多人在react怎么管理狀態(tài)問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”react怎么管理狀態(tài)”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
創(chuàng)新互聯(lián)主營清流網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件開發(fā),清流h5微信小程序開發(fā)搭建,清流網(wǎng)站營銷推廣歡迎清流等地區(qū)企業(yè)咨詢
react管理狀態(tài)的工具:1、利用hooks進行狀態(tài)管理;2、利用Redux進行狀態(tài)管理,這種方式的配套工具比較齊全,可以自定義各種中間件;3、利用Mobx進行狀態(tài)管理,它通過透明的函數(shù)響應(yīng)式編程使得狀態(tài)管理變得簡單和可擴展。
本教程操作環(huán)境:Windows7系統(tǒng)、react17.0.1版、Dell G3電腦。
jQuery 時代,JS 代碼中混雜 DOM 結(jié)構(gòu),各個流程龐雜交織時,就形成面條式代碼,當使用發(fā)布訂閱模型時,調(diào)試會一團亂麻。
jQuery 是針對 "過程" 的命令式編程,而那么多命令,最終都是為了更新 UI 中的 "數(shù)據(jù)",為什么不直接去改數(shù)據(jù)呢?
北京 → 上海,把 city="北京" 變?yōu)?city="上海" 就行。不管飛機火車步行拋錨,也不管路上會不會遇到王寶強,
現(xiàn)代前端框架的意義,就是問題解決思路的革新,把對 "過程" 的各種命令,變?yōu)榱藢?"狀態(tài)" 的描述。
什么是狀態(tài)?狀態(tài)就是 UI 中的動態(tài)數(shù)據(jù)。
2013 年 5 月 React 誕生。但 2015 年之前,大概都是 jQuery 的天下。2015 年 3 月 React 0.13.0 發(fā)布,帶來了 class 組件寫法。
在 React class 組件時代,狀態(tài)就是 this.state,使用 this.setState 更新。
為避免一團亂麻,React 引入了 "組件" 和 "單向數(shù)據(jù)流" 的理念。有了狀態(tài)與組件,自然就有了狀態(tài)在組件間的傳遞,一般稱為 "通信"。
父子通信較簡單,而深層級、遠距離組件的通信,則依賴于 "狀態(tài)提升" + props 層層傳遞。
于是,React 引入了 Context,一個用于解決組件 "跨級" 通信的官方方案。
但 Context 其實相當于 "狀態(tài)提升",并沒有額外的性能優(yōu)化,且寫起來比較啰嗦。
為優(yōu)化性能,一般會添加多個 Context,寫起來就更啰嗦。在項目沒那么復(fù)雜時,還不如層層傳遞簡單。
實用主義來說,"狀態(tài)管理" 就是為了解決組件間的 "跨級" 通信。
當然,在使用狀態(tài)管理庫時,其會帶來一些衍生的思維模式,比如如何組織 state,如何拆分公共邏輯、業(yè)務(wù)邏輯、組件邏輯等,但歸根結(jié)底,這些都不是核心緣由。
核心就是為了解決實際問題 —— 為了通信。其它的各種概念與哲學,都不是必要的。
Context 沒那么好用,React 官方也沒什么最佳實踐,于是一個個社區(qū)庫就誕生了。
目前比較常用的狀態(tài)管理方式有hooks、redux、mobx三種,下面我將詳細介紹一下這三類的使用方法以及分析各自的優(yōu)缺點,以供各位進行參考。
用hooks進行狀態(tài)管理主要有兩種方式:
useContext+useReducer
useState+useEffect
useContext+useReducer
使用方法
1.創(chuàng)建store和reducer以及全局context
src/store/reducer.ts
import React from "react"; // 初始狀態(tài) export const state = { count: 0, name: "ry", }; // reducer 用于修改狀態(tài) 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 () }
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 (); }; export default Count;count:{ctx.state.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 (); }; export default Name;name:{ctx.state.name}
useState+useEffect
使用方法
1.創(chuàng)建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.寫一個發(fā)布訂閱模式,讓組件訂閱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' } // 發(fā)布,所有訂閱者收到消息,執(zhí)行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 } // 狀態(tài)改變即發(fā)布 broadcast() } // useEffect + useState實現(xiàn)發(fā)布訂閱 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 () }
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 = () => { // 調(diào)用useGlobalStates()即訂閱globalStates() const { count } = useGlobalStates() return () } export default Countcount:{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 () } export default Countname:{name}
優(yōu)缺點分析
由于以上兩種都是采用hooks進行狀態(tài)管理,這里統(tǒng)一進行分析
優(yōu)點
代碼比較簡潔,如果你的項目比較簡單,只有少部分狀態(tài)需要提升到全局,大部分組件依舊通過本地狀態(tài)來進行管理。這時,使用 hookst進行狀態(tài)管理就挺不錯的。殺雞焉用牛刀。
缺點
兩種hooks管理方式都有一個很明顯的缺點,會產(chǎn)生大量的無效rerender,如上例中的Count和Name組件,當state.count改變后,Name組件也會rerender,盡管他沒有使用到state.count。這在大型項目中無疑是效率比較低的。
使用方法:
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' // 定義參數(shù)接口 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= { addReducer } const reducer: Reducer = combineReducers(reducers) export default reducer
3.創(chuàng)建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(, 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' // 聲明參數(shù)接口 interface Props { count: number add: (num: number) => void } // ReturnType獲取函數(shù)返回值類型,&交叉類型(用于多類型合并) // type Props = ReturnType& ReturnType const Count: FC = (props) => { const { count, add } = props return ( ) } // 這里相當于自己手動做了映射,只有這里映射到的屬性變化,組件才會rerender const mapStateToProps = (state: CombinedState) => ({ count: state.addReducer.count }) const mapDispatchToProps = (dispatch: Dispatch) => { return { add(num: number = 1) { // payload為參數(shù) dispatch({ type: types.ADD, payload: num }) } } } export default connect(mapStateToProps, mapDispatchToProps)(Count)count: {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' // 聲明參數(shù)接口 interface Props { name: string } const Name: FC= (props) => { const { name } = props console.log('NameRerendered') return ( ) } // name變化組件才會rerender const mapStateToProps = (state: CombinedState) => ({ name: state.addReducer.name }) // addReducer內(nèi)任意屬性變化組件都會rerender // const mapStateToProps = (state: CombinedState) => state.addReducer export default connect(mapStateToProps)(Name)name: {name}
優(yōu)缺點分析
優(yōu)點
組件會訂閱store中具體的某個屬性【mapStateToProps手動完成】,只要當屬性變化時,組件才會rerender,渲染效率較高
流程規(guī)范,按照官方推薦的規(guī)范和結(jié)合團隊風格打造一套屬于自己的流程。
配套工具比較齊全redux-thunk支持異步,redux-devtools支持調(diào)試
可以自定義各種中間件
缺點
state+action+reducer的方式不太好理解,不太直觀
非常啰嗦,為了一個功能又要寫reducer又要寫action,還要寫一個文件定義actionType,顯得很麻煩
使用體感非常差,每個用到全局狀態(tài)的組件都得寫一個mapStateToProps和mapDispatchToProps,然后用connect包一層,我就簡單用個狀態(tài)而已,咋就這么復(fù)雜呢
當然還有一堆的引入文件,100行的代碼用了redux可以變成120行,不過換個角度來說這也算增加了自己的代碼量
好像除了復(fù)雜也沒什么缺點了
MobX 是一個經(jīng)過戰(zhàn)火洗禮的庫,它通過透明的函數(shù)響應(yīng)式編程(transparently applying functional reactive programming - TFRP)使得狀態(tài)管理變得簡單和可擴展。
常規(guī)使用(mobx-react)
使用方法
1.引入mobx
yarn add mobx mobx-react -D
2.創(chuàng)建store
在/src/store目錄下創(chuàng)建你要用到的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(, 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) => { const { count, addCount } = props.store1 return ( ) } // 函數(shù)組件用Hoc,方法如下(本文統(tǒng)一使用函數(shù)組件) export default inject('store1')(observer(Count))count: {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) => { const { name } = props.store1 console.log('NameRerendered') return ( ) } // 函數(shù)組件用Hoc,方法如下(本文統(tǒng)一使用函數(shù)組件) export default inject('store1')(observer(Name))name: {name}
優(yōu)缺點分析:
優(yōu)點:
組件會自動訂閱store中具體的某個屬性,無需手動訂閱噢!【下文會簡單介紹下原理】只有當訂閱的屬性變化時,組件才會rerender,渲染效率較高
一個store即寫state,也寫action,這種方式便于理解,并且代碼量也會少一些
缺點:
當我們選擇的技術(shù)棧是React+Typescript+Mobx時,這種使用方式有一個非常明顯的缺點,引入的store必須要在props的type或interface定義過后才能使用(會增加不少代碼量),而且還必須指定這個store為可選的,否則會報錯(因為父組件其實沒有傳遞這個prop給子組件),這樣做還可能會致使對store取值時,提示可能為undefined,雖然能夠用“!”排除undefined,可是這種作法并不優(yōu)雅。
最佳實踐(mobx+hooks)
使用方法
1.引入mobx
同上
2.創(chuàng)建store
同上
3.導出store(結(jié)合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 () } // 函數(shù)組件用Hoc,方法如下(本文統(tǒng)一使用函數(shù)組件) export default observer(Count)count: {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 () } export default observer(Name)name: {name}
學習成本少,基礎(chǔ)知識非常簡單,跟 Vue 一樣的核心原理,響應(yīng)式編程。
一個store即寫state,也寫action,這種方式便于理解
組件會自動訂閱store中具體的某個屬性,只要當屬性變化時,組件才會rerender,渲染效率較高
成功避免了上一種使用方式的缺點,不用對使用的store進行interface或type聲明!
內(nèi)置異步action操作方式
代碼量真的很少,使用很簡單有沒有,強烈推薦!
過于自由:Mobx提供的約定及模版代碼很少,這導致開發(fā)代碼編寫很自由,如果不做一些約定,比較容易導致團隊代碼風格不統(tǒng)一,團隊建議啟用嚴格模式!
使用方式過于簡單
Mobx自動訂閱實現(xiàn)原理
基本概念
Observable //被觀察者,狀態(tài) Observer //觀察者,組件 Reaction //響應(yīng),是一類的特殊的 Derivation,可以注冊響應(yīng)函數(shù),使之在條件滿足時自動執(zhí)行。
建立依賴
我們給組件包的一層observer實現(xiàn)了這個功能
export default observer(Name)
組件每次mount和update時都會執(zhí)行一遍useObserver函數(shù),useObserver函數(shù)中通過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); // 結(jié)束收集 endBatch(); };
reaction.track里面的核心內(nèi)容是trackDerivedFunction
function trackDerivedFunction(derivation: IDerivation, f: () => T, context: any) { ... let result // 執(zhí)行回調(diào)f,觸發(fā)了變量(即組件的參數(shù))的 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 }
觸發(fā)依賴
Observable(被觀察者,狀態(tài))修改后,會調(diào)用它的set方法,然后再依次執(zhí)行該Observable之前收集的依賴函數(shù),觸發(fā)rerender。
組件更新
用組件更新來簡單闡述總結(jié)一下:mobx的執(zhí)行原理。
observer這個裝飾器(也可以是Hoc),對React組件的render方法進行track。
將render方法,加入到各個observable的依賴中。當observable發(fā)生變化,track方法就會執(zhí)行。
track中,還是先進行依賴收集,調(diào)用forceUpdate去更新組件,然后結(jié)束依賴收集。
每次都進行依賴收集的原因是,每次執(zhí)行依賴可能會發(fā)生變化。
到此,關(guān)于“react怎么管理狀態(tài)”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
文章題目:react怎么管理狀態(tài)
轉(zhuǎn)載來源:http://weahome.cn/article/gsdhoo.html