建立可觀察狀態
屬性、整個物件、陣列、映射和集合都可以變成可觀察的。讓物件可觀察的基本方法是使用 makeObservable
為每個屬性指定一個註釋。最重要的註釋是
observable
定義一個儲存狀態的可追蹤欄位。action
將方法標記為將修改狀態的動作。computed
標記一個將從狀態衍生新事實並快取其輸出的 getter。
makeObservable
用法
makeObservable(target, annotations?, options?)
此函數可用於使_現有_物件屬性可觀察。任何 JavaScript 物件(包括類別實例)都可以傳遞到 target
中。通常 makeObservable
用於類別的建構函式中,其第一個參數是 this
。annotations
參數將 註釋 映射到每個成員。只有被註釋的成員才會受到影響。
或者,可以在類別成員上使用像 @observable
這樣的裝飾器,而不是在建構函式中呼叫 makeObservable
。
衍生資訊並接受參數的方法(例如 findUsersOlderThan(age: number): User[]
)不能被註釋為 computed
- 當它們從反應中被呼叫時,它們的讀取操作仍然會被追蹤,但它們的輸出不會被記憶體化以避免記憶體洩漏。要記憶體化此類方法,您可以改用 MobX-utils computedFn {🚀}。
使用 override
註釋支援子類別化,但有一些限制(請參閱這裡的範例)。
import { makeObservable, observable, computed, action, flow } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
fetch: flow
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
**所有已註釋**的欄位都是**不可設定的**。
**所有不可觀察**(無狀態)的欄位(`action`、`flow`)都是**不可寫入的**。
使用現代裝飾器時,無需呼叫 makeObservable
,以下是基於裝飾器的類別的外觀。請注意,@observable
註釋應始終與 accessor
關鍵字一起使用。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable accessor value
constructor(value) {
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
請注意,類別也可以利用 makeAutoObservable
。範例中的差異僅說明 MobX 如何應用於不同的程式設計風格。
import { observable } from "mobx"
const todosById = observable({
"TODO-123": {
title: "find a decent task management system",
done: false
}
})
todosById["TODO-456"] = {
title: "close all tickets older than two weeks",
done: true
}
const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
與第一個使用 makeObservable
的範例相比,observable
支援向物件新增(和移除)_欄位_。這使得 observable
非常適合動態鍵控物件、陣列、映射和集合等集合。
要使用舊版裝飾器,應在建構函式中呼叫 makeObservable(this)
以確保裝飾器有效。
import { observable, computed, action, flow } from "mobx"
class Doubler {
@observable value
constructor(value) {
makeObservable(this)
this.value = value
}
@computed
get double() {
return this.value * 2
}
@action
increment() {
this.value++
}
@flow
*fetch() {
const response = yield fetch("/api/value")
this.value = response.json()
}
}
makeAutoObservable
用法
makeAutoObservable(target, overrides?, options?)
makeAutoObservable
就像吃了類固醇的 makeObservable
,因為它預設會推斷所有屬性。但是,您可以使用 overrides
參數用特定的註釋覆蓋預設行為 - 特別是 false
可用於完全排除屬性或方法被處理。請查看上面的程式碼範例。
與使用 makeObservable
相比,makeAutoObservable
函數可以更簡潔且更易於維護,因為無需明確提及新成員。但是,makeAutoObservable
不能用於具有父類別或子類別化的類別。
推斷規則
- 所有_自有_屬性都變為
observable
。 - 所有
getter
都變為computed
。 - 所有
setter
都變為action
。 - 所有_函數_都變為
autoAction
。 - 所有_產生器_函數都變為
flow
。(請注意,在某些轉譯器配置中無法檢測到產生器函數,如果 flow 沒有按預期工作,請確保明確指定flow
。) - 在
overrides
參數中標記為false
的成員將不會被註釋。例如,將其用於唯讀欄位,例如識別碼。
observable
用法
observable(source, overrides?, options?)
@observable accessor
_(欄位裝飾器)_
observable
註釋也可以作為函數呼叫,一次使整個物件可觀察。 source
物件將被複製,所有成員都將變為可觀察的,類似於 makeAutoObservable
的做法。同樣,可以提供 overrides
映射來指定特定成員的註釋。請查看上面的程式碼區塊以獲取範例。
observable
返回的物件將是一個 Proxy,這意味著稍後新增到物件的屬性也將被擷取並變為可觀察的(除非已停用 proxy 使用)。
observable
方法也可以使用集合類型呼叫,例如 陣列、映射 和 集合。這些也將被複製並轉換為其可觀察的對應物。
**範例:** 可觀察陣列
以下範例建立一個可觀察物件,並使用 autorun
觀察它。使用映射和集合的工作方式類似。
import { observable, autorun } from "mobx"
const todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
])
autorun(() => {
console.log(
"Remaining:",
todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
)
})
// Prints: 'Remaining: Make coffee'
todos[0].completed = false
// Prints: 'Remaining: Spoil tea, Make coffee'
todos[2] = { title: "Take a nap", completed: false }
// Prints: 'Remaining: Spoil tea, Make coffee, Take a nap'
todos.shift()
// Prints: 'Remaining: Make coffee, Take a nap'
可觀察陣列具有一些額外的便捷工具函數
clear()
從陣列中移除所有當前項目。replace(newItems)
將陣列中所有現有項目替換為新項目。remove(value)
按值從陣列中移除單個項目。如果找到並移除了該項目,則返回true
。
**注意:**基本類型和類別實例永遠不會轉換為可觀察物件
基本類型值不能被 MobX 設為可觀察的,因為它們在 JavaScript 中是不可變的(但它們可以被 裝箱)。儘管此機制通常在函式庫之外沒有用處。
類別實例永遠不會通過將它們傳遞給 `observable` 或將它們分配給 `observable` 屬性而自動變為可觀察的。使類別成員可觀察被認為是類別建構函式的職責。
{🚀} **提示:** observable(代理)與 makeObservable(非代理)
make(Auto)Observable
和 observable
之間的主要區別在於,前者會修改您作為第一個參數傳入的物件,而 observable
會建立一個可觀察的_副本_。
第二個區別是 observable
建立一個 Proxy
物件,以便能夠在您將物件用作動態查詢映射的情況下攔截未來的屬性新增。如果您想要使之可觀察的物件具有規則結構,其中所有成員都是預先知道的,我們建議使用 makeObservable
,因為非代理物件的速度稍快,並且它們在除錯器和 console.log
中更容易檢查。
因此,make(Auto)Observable
是在工廠函數中使用的推薦 API。請注意,可以將 { proxy: false }
作為選項傳遞給 observable
以獲取非代理副本。
可用的註釋
註釋 | 說明 |
---|---|
observable observable.deep | 定義一個可追蹤的欄位來儲存狀態。如果可能,任何賦值給 observable 的值都會根據其類型自動轉換為(深度)observable 、autoAction 或 flow 。只有 plain object 、array 、Map 、Set 、function 、generator function 可以轉換。類別實例和其他類型則保持不變。 |
observable.ref | 類似於 observable ,但只會追蹤重新賦值。賦值的值會被完全忽略,**不會**自動轉換為 observable /autoAction /flow 。例如,如果您打算在可觀察欄位中儲存不可變數據,請使用此選項。 |
observable.shallow | 類似於 observable.ref ,但適用於集合。任何賦值的集合都會變成可觀察的,但集合本身的內容不會變成可觀察的。 |
observable.struct | 類似於 observable ,但任何結構上與目前值相同的賦值都會被忽略。 |
action | 將方法標記為會修改狀態的動作。查看 動作 以了解更多詳細資訊。不可寫入。 |
action.bound | 類似於 action,但也會將動作綁定到實例,以便 this 永遠被設定。不可寫入。 |
computed | 可以用在 getter 上,將其宣告為可快取的衍生值。查看 computeds 以了解更多詳細資訊。 |
computed.struct | 類似於 computed ,但如果重新計算後的結果與之前的結果結構上相同,則不會通知任何觀察者。 |
true | 推斷最佳註釋。查看 makeAutoObservable 以了解更多詳細資訊。 |
false | 明確不註釋此屬性。 |
flow | 建立一個 flow 來管理非同步流程。查看 flow 以了解更多詳細資訊。請注意,TypeScript 中推斷的返回類型可能不準確。不可寫入。 |
flow.bound | 類似於 flow,但也會將 flow 綁定到實例,以便 this 永遠被設定。不可寫入。 |
override | 適用於被子類別覆寫的繼承 action 、flow 、computed 、action.bound 。. |
autoAction | 不應明確使用,但 makeAutoObservable 在底層使用它來標記可以根據其調用上下文充當動作或衍生的方法。它將在運行時確定函數是衍生還是動作。 |
make(Auto)Observable
只支援已定義的屬性。請確保您的 編譯器設定正確,或者作為解決方法,在使用make(Auto)Observable
之前為所有屬性賦值。如果沒有正確的設定,則已宣告但未初始化的欄位(例如在class X { y; }
中)將無法被正確擷取。makeObservable
只能註釋由其自身類別定義宣告的屬性。如果子類別或父類別引入了可觀察欄位,則它必須自行調用makeObservable
來處理這些屬性。options
參數只能提供一次。傳遞的options
是 *「黏著的」*,之後無法更改(例如,在 子類別 中)。- 每個欄位只能註釋一次(
override
除外)。欄位註釋或設定無法在 子類別 中更改。 - 所有已註釋的非純物件(類別)欄位都是 **不可設定的**。
可以使用configure({ safeDescriptors: false })
{🚀☣️} 禁用。. - **所有不可觀察**(無狀態)的欄位(`action`、`flow`)都是**不可寫入的**。
可以使用configure({ safeDescriptors: false })
{🚀☣️} 禁用。. - 只有定義在**原型**上的 **
action
、computed
、flow
、action.bound
** 可以被子類別**覆寫**。. - 預設情況下,*TypeScript* 不允許您註釋 **私有** 欄位。這可以通過將相關的私有欄位作為泛型參數顯式傳遞來克服,如下所示:
makeObservable<MyStore, "privateField" | "privateField2">(this, { privateField: observable, privateField2: observable })
- **調用
make(Auto)Observable
** 並提供註釋必須**無條件地**完成,因為這可以快取推斷結果。 - **不支援**在調用 **
make(Auto)Observable
** 後**修改原型**。 - **不支援** _EcmaScript_ **私有** 欄位(**
#field
**)。使用 _TypeScript_ 時,建議改用private
修飾符。 - **不支援**在單一繼承鏈中**混合註釋和裝飾器** - 例如,您不能將裝飾器用於父類別,將註釋用於子類別。
makeObservable
、extendObservable
不能用於其他內建可觀察類型(ObservableMap
、ObservableSet
、ObservableArray
等)makeObservable(Object.create(prototype))
會將屬性從prototype
複製到建立的物件,並使其成為observable
。這種行為是錯誤的、出乎意料的,因此已被**棄用**,並且很可能在未來的版本中更改。不要依賴它。
上述 API 採用一個可選的 options
參數,它是一個支援以下選項的物件
autoBind: true
預設使用action.bound
/flow.bound
,而不是action
/flow
。不影響明確註釋的成員。deep: false
預設使用observable.ref
,而不是observable
。不影響明確註釋的成員。name: <string>
為物件提供一個除錯名稱,該名稱會在錯誤訊息和反射 API 中列印。proxy: false
強制observable(thing)
使用非 proxy 實作。如果物件的形狀不會隨著時間改變,這是一個不錯的選擇,因為非代理物件更容易除錯且速度更快。此選項**不適用於**make(Auto)Observable
,請參閱 避免代理。
**注意:**選項是*黏著的*,只能提供一次
options
參數只能提供給尚未成為可觀察的 target
。可觀察物件初始化後,無法更改選項。
選項儲存在目標上,並被後續的
makeObservable
/extendObservable
調用遵守。您不能在 子類別 中傳遞不同的選項。
有時需要將可觀察的數據結構轉換回其原生對應項。例如,將可觀察物件傳遞給無法追蹤可觀察物件的 React 元件時,或者要取得不應進一步變動的副本時。
要淺層轉換集合,可以使用常用的 JavaScript 機制
const plainObject = { ...observableObject }
const plainArray = observableArray.slice()
const plainMap = new Map(observableMap)
要將數據樹遞迴轉換為純物件,可以使用 toJS
工具。對於類別,建議實作 toJSON()
方法,因為它會被 JSON.stringify
擷取。
到目前為止,上述大多數範例都傾向於類別語法。MobX 原則上對此沒有意見,而且可能也有同樣多的 MobX 使用者使用純物件。然而,類別的一個小優點是它們具有更容易發現的 API,例如 TypeScript。此外,instanceof
檢查對於類型推斷非常有效,並且類別實例不會包裝在 Proxy
物件中,這讓它們在除錯器中具有更好的體驗。最後,類別受益於許多引擎優化,因為它們的形狀是可預測的,並且方法在原型上共享。但是,大量的繼承模式很容易變成陷阱,因此如果您使用類別,請保持簡單。因此,即使稍微傾向於使用類別,我們仍然鼓勵您偏離這種風格,如果那更適合您的話。