Mobx實(shí)現(xiàn)React?應(yīng)用的狀態(tài)管理詳解
MobX
MobX 是一個(gè)狀態(tài)管理庫(kù),它會(huì)自動(dòng)收集并追蹤依賴,開(kāi)發(fā)人員不需要手動(dòng)訂閱狀態(tài),當(dāng)狀態(tài)變化之后 MobX 能夠精準(zhǔn)更新受影響的內(nèi)容,另外它不要求 state 是可 JSON 序列化的,也不要求state 是 immutable,MobX 推薦的數(shù)據(jù)流如下圖所示:
本文先以一個(gè) demo 單獨(dú)介紹 Mobx 的用法,再介紹如何將 Mobx 與 React 結(jié)合實(shí)現(xiàn) React 應(yīng)用程序的狀態(tài)管理。
從一個(gè) demo 開(kāi)始
這部分用 MobX + TypeScript 實(shí)現(xiàn)一個(gè) TODO List 的 demo,MobX 的版本為 6.5.0,TypeScript 的版本為 4.5.4,將 TypeScript 編譯器配置項(xiàng) useDefineForClassFields 設(shè)置為 true。
創(chuàng)建類并將其轉(zhuǎn)化成可觀察對(duì)象
創(chuàng)建 ToDoItem 類和 ToDoList 類,ToDoItem 類的代碼如下:
import { makeObservable, observable, action } from 'mobx' class ToDoItem { id: number name: string status: 0 | 1 changeStatus(status: Status) { this.status = status } constructor(name: string) { this.id = Uid ++ this.name = name this.status = 0 // 注意這里 makeObservable(this, { status: observable, changeStatus: action }) } }
用 makeObservable 將 ToDoItem 實(shí)例變成可觀察的,用 observable 標(biāo)記 status 字段,讓 MobX 跟蹤它的變化,changeStatus 方法用于修改 status 的值,所以用action標(biāo)記它。
ToDoList 類比 ToDoItem 類復(fù)雜一些,它收集 Todo-List Demo 需要的全部數(shù)據(jù),代碼如下:
import { makeObservable, observable, action, computed, runInAction } from 'mobx' class ToDoList { searchStatus?: 0 | 1 list: ToDoItem[] = [] get displayList() { if (!this.searchStatus) { return this.list } else { return this.list.filter(item => item.status === this.searchStatus) } } changeStatus(searchStatus: Status | undefined) { this.searchStatus = searchStatus } addItem(name: string) { this.list.push(new ToDoItem(name)) } async fetchInitData() { await waitTime() // 注意這里 runInAction(() => { this.list = [new ToDoItem('one'), new ToDoItem('two')] }) } constructor() { makeObservable(this, { searchStatus: observable, list: observable, displayList: computed, changeStatus: action, addItem: action }) } }
與 ToDoItem 相比,ToDoList 多使用了 computed 標(biāo)記,這是因?yàn)?displayList 的值由 searchStatus 和 list 通過(guò)一個(gè)純函數(shù)計(jì)算而來(lái),所以它被標(biāo)記為 computed。fetchInitData 是一個(gè)異步方法,在其中用 runInAction 創(chuàng)建一個(gè)立即執(zhí)行的 action 去修改 list 的值,從 fetchInitData 的實(shí)現(xiàn)可以看出,異步修改 state 和同步修改 state 沒(méi)有差別,只要保證 state 是在 action 中修改的即可。
使用可觀察對(duì)象
在上一步的 ToDoList 和 ToDoItem 的構(gòu)造函數(shù)中,我們調(diào)用了 makeObservable 方法,并用合適的注解去標(biāo)記實(shí)例字段,接下來(lái)用一段代碼驗(yàn)證 MobX 是否按照要求跟蹤state的變化。代碼如下:
import { autorun} from 'mobx' autorun(() => { console.log(toDoList.list.length) }) // line A autorun(() => { console.log(toDoList.list) }) // line B
autorun 接收一個(gè)函數(shù),該函數(shù)同步執(zhí)行過(guò)程中訪問(wèn)的 state 或計(jì)算值發(fā)生變化時(shí),它會(huì)自動(dòng)運(yùn)行,另外,調(diào)用 autorun 時(shí),該函數(shù)也會(huì)運(yùn)行一次。使用 toDoList.addItem 方法往 list 數(shù)組中 push 一個(gè)事項(xiàng),你會(huì)發(fā)現(xiàn)上述 line A 的函數(shù)會(huì)運(yùn)行,但是 line B 的函數(shù)不會(huì)運(yùn)行;使用 toDoList.fetchInitData 方法給 list 數(shù)組賦值,line A 和 line B 的函數(shù)都會(huì)運(yùn)行,出現(xiàn)這種差異是因?yàn)?autorun 使用全等(===)運(yùn)算符確定兩個(gè)值是否相等,但它認(rèn)為 NaN 等于 NaN 。
用如下一段代碼驗(yàn)證 MobX 是否按照要求跟蹤 ToDoItem 實(shí)例的 state 的變化:
import { autorun} from 'mobx' autorun(() => { if (toDoList.list.length) { console.log(toDoList.list[0]?.status) } }) reaction(() => toDoList.list.length, () => { toDoList.list[0].changeStatus(1)// 修改status的值 })
當(dāng) reaction 的第一個(gè)參數(shù)返回 true 時(shí),它的第二個(gè)參數(shù)會(huì)自動(dòng)執(zhí)行,上述代碼在 reaction 中修改 toDoItem 的 status 字段,修改之后 autorun 能成功運(yùn)行一次。
MobX 與 React 集成
現(xiàn)在將上一步的 TODO List 與 React 結(jié)合,為此需要安裝 mobx-react-lite 或 mobx-react,mobx-react 比 mobx-react-lite 的功能更多,同時(shí)它的體積也更大,如果你的項(xiàng)目只使用函數(shù)組件,那么推薦安裝 mobx-react-lite 而非 mobx-react,為了演示更多的用法本小節(jié)安裝 mobx-react。另外,本小節(jié)會(huì)用到裝飾器語(yǔ)法,所以要將 TypeScript 編譯器配置項(xiàng) experimentalDecorators 設(shè)置為true。下面是一個(gè) MobX + React 的簡(jiǎn)單示例:
import { observer } from 'mobx-react' import toDoList, { Status } from '../../mobx/todo' const ToDoListDemoGlobalInstance= observer( class extends React.Component<{}, {}> { componentDidMount() { // 3s之后修改 searchStatus 的值 setTimeout(() => { toDoList.changeStatus(Status.finished) }, 3000); } render() { return ( <div>searchStatus: {toDoList.searchStatus}</div> ) } } )
observer 是一個(gè)高階組件,它會(huì)訂閱組件在渲染期間訪問(wèn)的可觀察對(duì)象,可觀察對(duì)象指的是用 makeAutoObservable 、makeObservable 或 observable 轉(zhuǎn)換之后的對(duì)象,當(dāng)組件渲染期間訪問(wèn)的 state 和計(jì)算值發(fā)生變化時(shí),組件會(huì)重新渲染。上述代碼,組件被裝載 3s 后將修改 searchStatus 的值,由于 render 方法訪問(wèn)了 searchStatus 的值,所以組件會(huì)重新渲染。observer 除了以高階組件的形式使用之外,還能以裝飾器的形式使用。
在組件中使用可觀察對(duì)象
下面介紹 6 種在組件中使用 MobX 可觀察對(duì)象的寫法。
1. 訪問(wèn)全局的類實(shí)例
上一個(gè)示例代碼便是在組件中直接訪問(wèn)全局的類實(shí)例,在這里不再舉更多的示例代碼。
2. 通過(guò) props
這種方式是指將可觀察對(duì)象通過(guò) props 的形式傳遞到組件中,代碼如下:
import { observer } from 'mobx-react' import toDoList, { Status } from '../../mobx/todo' @observer class ToDoListDemoByProps extends React.Component<{toDoList: ToDoList}, {}> { componentDidMount() { setTimeout(() => { toDoList.changeStatus(Status.finished) }, 3000); } render() { // 讀取props中的可觀察對(duì)象 return ( <div>ToDoListDemoByProps - searchStatus: {this.props.toDoList.searchStatus}</div> ) } } //使用ToDoListDemoByProps <ToDoListDemoByProps toDoList={toDoList}/>
3. 通過(guò) React Context
這種方式是指通過(guò) React Context 讓可觀察對(duì)象在整個(gè)被 Context.Provider 包裹的組件樹中共享,代碼如下:
import { observer } from 'mobx-react' import toDoList, { Status, ToDoList } from '../../mobx/todo' // 創(chuàng)建一個(gè)用observer包裹的函數(shù)組件 const ToDoListDemoByContext = observer(() => { // 在函數(shù)組件中使用Context const context = useContext(todoContext); useEffect(() => { setTimeout(() => { context.changeStatus(Status.finished) }, 3000); }) return ( <div>ToDoListDemoByContext - searchStatus: {context.searchStatus}</div> ) }) // 往Context傳值 <todoContext.Provider value={toDoList}> <ToDoListDemoByContext/> </todoContext.Provider>
4. 在組件中實(shí)例化 observable class 并存儲(chǔ)它的實(shí)例
這種方式指的是在組件作用域中實(shí)例化類,并且將結(jié)果保存到組件的某個(gè)字段中,如果在函數(shù)組件中使用這種方式,那么還需要用到 useState。代碼如下:
const ToDoListFuncDemoLocalInstance= observer(() => { // 實(shí)例化類 const [ todoList ] = useState(() => new ToDoList()) useEffect(() => { setTimeout(() => { // 使用實(shí)例方法更新?tīng)顟B(tài) todoList.changeStatus(Status.finished) }, 3000); }) return ( <div>ToDoListDemoLocalInstance - searchStatus: {todoList.searchStatus}</div> ) })
對(duì)于類組件而言,只需要將 new ToDoList() 的結(jié)果保存在它的實(shí)例屬性上,之后在組件中訪問(wèn)該實(shí)例屬性,代碼如下:
@observer class ToDoListClassDemoLocalInstance extends React.Component<{}, {}> { todoList = new ToDoList() // other }
5. 在組件中調(diào)用 observable 方法創(chuàng)建可觀察對(duì)象
這種方式不使用類去創(chuàng)建可觀察對(duì)象,而是使用 observable 方法創(chuàng)建可觀察對(duì)象,與第 4 種方式一樣,如果在函數(shù)組件中還要用到 useState,代碼如下
import { observable } from 'mobx' const LocalObservableDemo = observer(() => { // 調(diào)用observable const [counter] = useState(() => observable({ count: 0, addCount() { this.count ++ } })) return <> <div>{counter.count}</div> <button onClick={() => counter.addCount()}>add</button> </> })
上述代碼使用 mobx 導(dǎo)出的 observable 方法創(chuàng)建一個(gè)可觀察對(duì)象,并在函數(shù)組件使用該對(duì)象,當(dāng)它的 count 屬性值發(fā)生變化時(shí),組件將重新渲染。對(duì)于類組件而言只需要將 observable 函數(shù)的結(jié)果保存到實(shí)例屬性上即可。
6. 在函數(shù)組件中使用 useLocalObservable
useLocalObservable 是 useState + observable 簡(jiǎn)寫版本,只能在函數(shù)組件中使用,代碼如下:
import { observer, useLocalObservable } from 'mobx-react' const UseLocalObservableDemo = observer(() => { const counter = useLocalObservable(() => ({ count: 0, addCount() { this.count ++ } })) return <> <div>{counter.count}</div> <button onClick={() => counter.addCount()}>add</button> </> })
對(duì)于函數(shù)組件而言,useLocalObservable 只是一個(gè)自定義Hook,它返回一個(gè)可觀察對(duì)象。
有多種方式讓組件在渲染階段使用可觀察對(duì)象,不管是哪種方式,組件都必須具備觀察能力,否則,當(dāng)渲染期間訪問(wèn)的 state 和計(jì)算值發(fā)生變化時(shí),組件不會(huì)重新渲染。筆者在使用 MobX 做狀態(tài)管理時(shí),最常用的方式是第 1 和第 2 種。第 4、5、6 種方式必要性不大。
讓組件具備觀察能力
observer 是讓組件具備觀察能力最常見(jiàn)的方式,在這里介紹另一種讓組件具備觀察能力的方式,即:Observer組件。用法如下:
import { Observer } from 'mobx-react' class ObservableDemo extends React.Component<{},{}> { render() { return ( <> <div>{toDoList.searchStatus || '-'}</div> {/** lineA */} <Observer> {() => <div>{toDoList.searchStatus || '-'}</div>} {/** lineB */} </Observer> </> ) } }
Observer 組件會(huì)創(chuàng)建一個(gè)匿名的觀察區(qū)域,在上述代碼中,如果 toDoList.searchStatus 的值發(fā)生變化,那么 lineB 會(huì)重新渲染,但是 lineA 不會(huì)重新渲染。
總結(jié)
將 MobX 與 React 結(jié)合在一起的關(guān)鍵在于用 observer 包裹組件以及在組件中讀取可觀察對(duì)象,observer 不關(guān)心可觀察對(duì)象從哪里來(lái),也不關(guān)心如何讀取可觀察對(duì)象,只關(guān)心在組件中可觀察對(duì)象是否可讀。對(duì)于習(xí)慣面向?qū)ο缶幊痰墓こ處煻裕?MobX 做狀態(tài)管理會(huì)比用 Redux 做狀態(tài)管理更得心應(yīng)手。
以上就是Mobx 實(shí)現(xiàn) React 應(yīng)用的狀態(tài)管理的詳細(xì)內(nèi)容,更多關(guān)于Mobx React 應(yīng)用狀態(tài)管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文詳解ReactNative狀態(tài)管理rematch使用
這篇文章主要為大家介紹了ReactNative狀態(tài)管理rematch使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03react使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能(完整代碼)
最近在做一個(gè)后臺(tái)管理項(xiàng)目,涉及到react相關(guān)知識(shí),項(xiàng)目需求需要在表單中帶附件提交,怎么實(shí)現(xiàn)這個(gè)功能呢?下面小編給大家?guī)?lái)了react使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能,一起看看吧2021-06-06React中mobx和redux的區(qū)別及說(shuō)明
這篇文章主要介紹了React中mobx和redux的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06