使用 action 更新狀態
用法
action
(註釋)action(fn)
action(name, fn)
@action
(方法/欄位裝飾器)
所有應用程式都有 action。Action 是任何修改狀態的程式碼片段。原則上,action 總是響應事件而發生。例如,點擊按鈕、輸入變更、收到 websocket 訊息等。
MobX 要求您宣告您的 action,儘管 makeAutoObservable
可以自動化大部分的工作。Action 可以幫助您更好地組織程式碼,並提供以下效能優勢:
它們在 交易 內執行。在最外層的 action 完成之前,不會執行任何反應,這保證了在 action 期間產生的中間值或不完整值,在 action 完成之前,對應用程式的其餘部分不可見。
預設情況下,不允許在 action 之外更改狀態。這有助於在您的程式碼庫中清楚地識別狀態更新發生的位置。
action
註釋應僅用於意圖*修改*狀態的函式。派生資訊(執行查找或篩選資料)的函式*不應*標記為 action,以允許 MobX 追蹤其呼叫。標記為 action
的成員將無法列舉。
範例
import { makeObservable, observable, action } from "mobx"
class Doubler {
value = 0
constructor() {
makeObservable(this, {
value: observable,
increment: action
})
}
increment() {
// Intermediate states will not become visible to observers.
this.value++
this.value++
}
}
import { observable, action } from "mobx"
class Doubler {
@observable accessor value = 0
@action
increment() {
// Intermediate states will not become visible to observers.
this.value++
this.value++
}
}
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor() {
makeAutoObservable(this)
}
increment() {
this.value++
this.value++
}
}
import { makeObservable, observable, action } from "mobx"
class Doubler {
value = 0
constructor() {
makeObservable(this, {
value: observable,
increment: action.bound
})
}
increment() {
this.value++
this.value++
}
}
const doubler = new Doubler()
// Calling increment this way is safe as it is already bound.
setInterval(doubler.increment, 1000)
import { observable, action } from "mobx"
const state = observable({ value: 0 })
const increment = action(state => {
state.value++
state.value++
})
increment(state)
import { observable, runInAction } from "mobx"
const state = observable({ value: 0 })
runInAction(() => {
state.value++
state.value++
})
action
包裝函式
使用 為了盡可能利用 MobX 的交易性質,應盡可能將 action 向外傳遞。如果類別方法修改了狀態,則將其標記為 action 是一個好習慣。將事件處理程式標記為 action 更好,因為最外層的交易才是最重要的。一個未標記的事件處理程式,即使它依序呼叫兩個 action,仍然會產生兩個交易。
為了幫助建立基於 action 的事件處理程式,action
不僅是一個註釋,還是一個高階函式。它可以將函式作為參數呼叫,在這種情況下,它將返回一個具有相同簽章的 action
包裝函式。
例如,在 React 中,可以如下所示包裝 onClick
處理程式。
const ResetButton = ({ formState }) => (
<button
onClick={action(e => {
formState.resetPendingUploads()
formState.resetValues()
e.preventDefault()
})}
>
Reset form
</button>
)
為了方便除錯,建議為包裝函式命名,或將名稱作為第一個參數傳遞給 action
。
**注意:** action 是未被追蹤的
action 的另一個特性是它們是 未被追蹤的。當從副作用或計算值內部呼叫 action 時(非常罕見!),action 讀取的可觀察物件不會被計入衍生值的依賴項中。
makeAutoObservable
、extendObservable
和 observable
使用一種稱為 autoAction
的特殊 action
,它將在執行時確定該函式是衍生值還是 action。
action.bound
用法
action.bound
(註釋)
action.bound
註釋可用於將方法自動綁定到正確的實例,以便在函式內部始終正確綁定 this
。
**提示:** 使用 makeAutoObservable(o, {}, { autoBind: true })
自動綁定所有 action 和流程
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor() {
makeAutoObservable(this, {}, { autoBind: true })
}
increment() {
this.value++
this.value++
}
*flow() {
const response = yield fetch("http://example.com/value")
this.value = yield response.json()
}
}
runInAction
用法
runInAction(fn)
使用此工具建立一個立即呼叫的臨時 action。在非同步流程中很有用。查看上面的程式碼區塊以獲取範例。
Action 與繼承
只有在**原型**上定義的 action 才能被子類別**覆蓋**
class Parent {
// on instance
arrowAction = () => {}
// on prototype
action() {}
boundAction() {}
constructor() {
makeObservable(this, {
arrowAction: action
action: action,
boundAction: action.bound,
})
}
}
class Child extends Parent {
// THROWS: TypeError: Cannot redefine property: arrowAction
arrowAction = () => {}
// OK
action() {}
boundAction() {}
constructor() {
super()
makeObservable(this, {
arrowAction: override,
action: override,
boundAction: override,
})
}
}
要將單個 *action* **綁定**到 this
,可以使用 action.bound
代替 *箭頭函式*。
請參閱 **子類別化** 以獲取更多資訊。
非同步 Action
本質上,非同步流程在 MobX 中不需要任何特殊處理,因為所有反應都會自動更新,而不管它們發生的時間點為何。由於可觀察物件是可變的,因此在 action 期間保留對它們的參考通常是安全的。但是,在非同步流程中更新可觀察物件的每個步驟(tick)都應標記為 action
。可以通過利用上述 API 以多種方式實現,如下所示。
例如,在處理 Promise 時,更新狀態的處理程式應為 action 或應使用 action
包裝,如下所示。
Promise 解析處理程式會被內嵌處理,但在原始 action 完成後執行,因此它們需要由 action
包裝
import { action, makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(
action("fetchSuccess", projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}),
action("fetchError", error => {
this.state = "error"
})
)
}
}
如果 Promise 處理程式是類別欄位,它們將被 makeAutoObservable
自動包裝在 action
中
import { makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(this.projectsFetchSuccess, this.projectsFetchFailure)
}
projectsFetchSuccess = projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
}
projectsFetchFailure = error => {
this.state = "error"
}
}
在 await
之後的任何步驟都不在同一個 tick 中,因此它們需要 action 包裝。在這裡,我們可以利用 runInAction
import { runInAction, makeAutoObservable } from "mobx"
class Store {
githubProjects = []
state = "pending" // "pending", "done" or "error"
constructor() {
makeAutoObservable(this)
}
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
runInAction(() => {
this.githubProjects = filteredProjects
this.state = "done"
})
} catch (e) {
runInAction(() => {
this.state = "error"
})
}
}
}
import { flow, makeAutoObservable, flowResult } from "mobx"
class Store {
githubProjects = []
state = "pending"
constructor() {
makeAutoObservable(this, {
fetchProjects: flow
})
}
// Note the star, this a generator function!
*fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
// Yield instead of await.
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
return projects
} catch (error) {
this.state = "error"
}
}
}
const store = new Store()
const projects = await flowResult(store.fetchProjects())
使用 flow 代替 async / await {🚀}
用法
flow
(註釋)flow(function* (args) { })
@flow
(方法裝飾器)
flow
包裝器是 async
/ await
的一個可選替代方案,它可以更輕鬆地使用 MobX action。 flow
將 產生器函式 作為其唯一輸入。在產生器內部,您可以通過 yield 它們來鏈接 Promise(而不是 await somePromise
,您編寫 yield somePromise
)。然後,flow 機制將確保產生器在 yield 的 Promise 解析時繼續或拋出錯誤。
因此 flow
是 async
/ await
的替代方案,不需要任何進一步的 action
包裝。它可以如下應用:
- 將
flow
包裝在您的非同步函式周圍。 - 使用
function *
代替async
。 - 使用
yield
代替await
。
上面的 flow
+ 產生器函式 範例展示了實際應用。
請注意,flowResult
函式僅在使用 TypeScript 時才需要。由於使用 flow
裝飾方法,它會將返回的產生器包裝在一個 Promise 中。然而,TypeScript 並不知道這種轉換,因此 flowResult
將確保 TypeScript 意識到這種類型變化。
makeAutoObservable
等方法會自動推斷產生器為 flow
。使用 flow
標註的成員將不可列舉。
{🚀} 注意:在物件欄位上使用 flow
flow
與 action
類似,可以直接用於包裝函式。上述範例也可以寫成如下
import { flow, makeObservable, observable } from "mobx"
class Store {
githubProjects = []
state = "pending"
constructor() {
makeObservable(this, {
githubProjects: observable,
state: observable,
})
}
fetchProjects = flow(function* (this: Store) {
this.githubProjects = []
this.state = "pending"
try {
// yield instead of await.
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
} catch (error) {
this.state = "error"
}
})
}
const store = new Store()
const projects = await store.fetchProjects()
好處是我們不再需要 flowResult
,缺點是需要為 this
指定類型以確保其類型被正確推斷。
flow.bound
用法
flow.bound
(註釋)
flow.bound
註釋可以用於自動將方法綁定到正確的實例,以便在函式內部始終正確綁定 this
。與 actions 類似,可以使用 autoBind
選項 預設綁定 flows。
取消 flows {🚀}
flows 的另一個好處是可以取消。flow
的返回值是一個 Promise,它會使用產生器函式最終返回的值來解析。返回的 Promise 有一個額外的 cancel()
方法,它會中斷正在運行的產生器並取消它。任何 try
/ finally
子句仍將被執行。
停用強制 actions {🚀}
預設情況下,MobX 6 及更高版本要求您使用 actions 來更改狀態。但是,您可以將 MobX 配置為停用此行為。請查看 enforceActions
章節。例如,這在單元測試設置中非常有用,因為警告並不總是有很大的價值。