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