網站首頁 編程語言 正文
MobX
MobX 是一個狀態管理庫,它會自動收集并追蹤依賴,開發人員不需要手動訂閱狀態,當狀態變化之后 MobX 能夠精準更新受影響的內容,另外它不要求 state 是可 JSON 序列化的,也不要求state 是 immutable,MobX 推薦的數據流如下圖所示:
本文先以一個 demo 單獨介紹 Mobx 的用法,再介紹如何將 Mobx 與 React 結合實現 React 應用程序的狀態管理。
從一個 demo 開始
這部分用 MobX + TypeScript 實現一個 TODO List 的 demo,MobX 的版本為 6.5.0,TypeScript 的版本為 4.5.4,將 TypeScript 編譯器配置項 useDefineForClassFields 設置為 true。
創建類并將其轉化成可觀察對象
創建 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 標記 status 字段,讓 MobX 跟蹤它的變化,changeStatus 方法用于修改 status 的值,所以用action標記它。
ToDoList 類比 ToDoItem 類復雜一些,它收集 Todo-List Demo 需要的全部數據,代碼如下:
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 標記,這是因為 displayList 的值由 searchStatus 和 list 通過一個純函數計算而來,所以它被標記為 computed。fetchInitData 是一個異步方法,在其中用 runInAction 創建一個立即執行的 action 去修改 list 的值,從 fetchInitData 的實現可以看出,異步修改 state 和同步修改 state 沒有差別,只要保證 state 是在 action 中修改的即可。
使用可觀察對象
在上一步的 ToDoList 和 ToDoItem 的構造函數中,我們調用了 makeObservable 方法,并用合適的注解去標記實例字段,接下來用一段代碼驗證 MobX 是否按照要求跟蹤state的變化。代碼如下:
import { autorun} from 'mobx'
autorun(() => { console.log(toDoList.list.length) }) // line A
autorun(() => { console.log(toDoList.list) }) // line B
autorun 接收一個函數,該函數同步執行過程中訪問的 state 或計算值發生變化時,它會自動運行,另外,調用 autorun 時,該函數也會運行一次。使用 toDoList.addItem 方法往 list 數組中 push 一個事項,你會發現上述 line A 的函數會運行,但是 line B 的函數不會運行;使用 toDoList.fetchInitData 方法給 list 數組賦值,line A 和 line B 的函數都會運行,出現這種差異是因為 autorun 使用全等(===)運算符確定兩個值是否相等,但它認為 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的值
})
當 reaction 的第一個參數返回 true 時,它的第二個參數會自動執行,上述代碼在 reaction 中修改 toDoItem 的 status 字段,修改之后 autorun 能成功運行一次。
MobX 與 React 集成
現在將上一步的 TODO List 與 React 結合,為此需要安裝 mobx-react-lite 或 mobx-react,mobx-react 比 mobx-react-lite 的功能更多,同時它的體積也更大,如果你的項目只使用函數組件,那么推薦安裝 mobx-react-lite 而非 mobx-react,為了演示更多的用法本小節安裝 mobx-react。另外,本小節會用到裝飾器語法,所以要將 TypeScript 編譯器配置項 experimentalDecorators 設置為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 轉換之后的對象,當組件渲染期間訪問的 state 和計算值發生變化時,組件會重新渲染。上述代碼,組件被裝載 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'
// 創建一個用observer包裹的函數組件
const ToDoListDemoByContext = observer(() => {
// 在函數組件中使用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 并存儲它的實例
這種方式指的是在組件作用域中實例化類,并且將結果保存到組件的某個字段中,如果在函數組件中使用這種方式,那么還需要用到 useState。代碼如下:
const ToDoListFuncDemoLocalInstance= observer(() => {
// 實例化類
const [ todoList ] = useState(() => new ToDoList())
useEffect(() => {
setTimeout(() => {
// 使用實例方法更新狀態
todoList.changeStatus(Status.finished)
}, 3000);
})
return (
<div>ToDoListDemoLocalInstance - searchStatus: {todoList.searchStatus}</div>
)
})
對于類組件而言,只需要將 new ToDoList() 的結果保存在它的實例屬性上,之后在組件中訪問該實例屬性,代碼如下:
@observer
class ToDoListClassDemoLocalInstance extends React.Component<{}, {}> {
todoList = new ToDoList()
// other
}
5. 在組件中調用 observable 方法創建可觀察對象
這種方式不使用類去創建可觀察對象,而是使用 observable 方法創建可觀察對象,與第 4 種方式一樣,如果在函數組件中還要用到 useState,代碼如下
import { observable } from 'mobx'
const LocalObservableDemo = observer(() => {
// 調用observable
const [counter] = useState(() => observable({
count: 0,
addCount() {
this.count ++
}
}))
return <>
<div>{counter.count}</div>
<button onClick={() => counter.addCount()}>add</button>
</>
})
上述代碼使用 mobx 導出的 observable 方法創建一個可觀察對象,并在函數組件使用該對象,當它的 count 屬性值發生變化時,組件將重新渲染。對于類組件而言只需要將 observable 函數的結果保存到實例屬性上即可。
6. 在函數組件中使用 useLocalObservable
useLocalObservable 是 useState + observable 簡寫版本,只能在函數組件中使用,代碼如下:
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>
</>
})
對于函數組件而言,useLocalObservable 只是一個自定義Hook,它返回一個可觀察對象。
有多種方式讓組件在渲染階段使用可觀察對象,不管是哪種方式,組件都必須具備觀察能力,否則,當渲染期間訪問的 state 和計算值發生變化時,組件不會重新渲染。筆者在使用 MobX 做狀態管理時,最常用的方式是第 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 組件會創建一個匿名的觀察區域,在上述代碼中,如果 toDoList.searchStatus 的值發生變化,那么 lineB 會重新渲染,但是 lineA 不會重新渲染。
總結
將 MobX 與 React 結合在一起的關鍵在于用 observer 包裹組件以及在組件中讀取可觀察對象,observer 不關心可觀察對象從哪里來,也不關心如何讀取可觀察對象,只關心在組件中可觀察對象是否可讀。對于習慣面向對象編程的工程師而言,用 MobX 做狀態管理會比用 Redux 做狀態管理更得心應手。
原文鏈接:https://juejin.cn/post/7173451717801934856
相關推薦
- 2023-07-03 CSS 中有五種常見的定位方式--詳解
- 2022-10-02 Linux?Shell?自動交互功能實現_linux shell
- 2022-05-28 C語言?超詳細講解鏈接器_C 語言
- 2023-11-25 全局后置路由守衛(afterEach)
- 2022-04-03 Python實現對相同數據分箱的小技巧分享_python
- 2022-04-14 解決Goland錯誤:$GOPATH/go.mod exists but should not
- 2022-06-17 關于Swagger優化的實戰記錄_實用技巧
- 2022-11-10 Python+Selenium實現瀏覽器的控制操作_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支