MobX 🇺🇦

MobX 🇺🇦

  • APIリファレンス
  • 中文
  • 한국어
  • スポンサー
  • GitHub

›ヒントとコツ

はじめに

  • MobXについて
  • このドキュメントについて
  • インストール
  • MobXの要点

MobXのコア

  • Observableな状態
  • アクション
  • 算出値
  • リアクション {🚀}
  • API

MobXとReact

  • Reactとの統合
  • Reactの最適化 {🚀}

ヒントとコツ

  • データストアの定義
  • リアクティビティの理解
  • サブクラス化
  • リアクティビティの分析 {🚀}
  • 引数付きの算出値 {🚀}
  • MobX-utils {🚀}
  • カスタムobservable {🚀}
  • 遅延observable {🚀}
  • コレクションユーティリティ {🚀}
  • インターセプトと監視 {🚀}

微調整

  • 構成 {🚀}
  • デコレーター {🚀}
  • MobX 4/5からの移行 {🚀}
編集

リアクティビティの理解

MobXは通常、期待どおりに反応します。つまり、使用例の90%ではMobXは「ただ動く」はずです。ただし、期待どおりに動作しないケースに遭遇することがあります。その時点で、MobXが何に反応するかをどのように判断するかを理解することは非常に重要になります。

MobXは、追跡された関数の実行中に読み取られる、既存のobservableなプロパティに反応します。

  • 「読み取り」とは、オブジェクトのプロパティを間接参照することです。これは、「ドットで接続する」(例:user.name)またはブラケット表記(例:user['name']、todos[3])または分割代入(例:const {name} = user)を使用して行うことができます。
  • 「追跡された関数」とは、computedの式、observerのReact関数コンポーネントのレンダリング、observerベースのReactクラスコンポーネントのrender()メソッド、およびautorun、reaction、およびwhenの最初のパラメーターとして渡される関数のことです。
  • 「実行中」とは、関数が実行されている間に読み取られるobservableのみが追跡されることを意味します。これらの値が追跡された関数によって直接的または間接的に使用されるかどうかは関係ありません。ただし、関数から「生成」されたものは追跡されません(例:setTimeout、promise.then、awaitなど)。

言い換えれば、MobXは以下には反応しません

  • observableから取得されたが、追跡された関数の外にある値
  • 非同期的に呼び出されたコードブロックで読み取られるobservable

MobXは値ではなくプロパティアクセスを追跡します

上記のルールを例で詳しく説明すると、次のobservableなインスタンスがあるとします。

class Message {
    title
    author
    likes
    constructor(title, author, likes) {
        makeAutoObservable(this)
        this.title = title
        this.author = author
        this.likes = likes
    }

    updateTitle(title) {
        this.title = title
    }
}

let message = new Message("Foo", { name: "Michel" }, ["Joe", "Sara"])

メモリ内では、次のようになります。緑色のボックスはobservableなプロパティを示します。値自体はobservableではないことに注意してください!

MobX reacts to changing references

MobXが基本的に行っていることは、関数で使用する矢印を記録することです。その後、これらの矢印の1つが変更されたとき、つまり他のものを参照し始めたときに、再実行されます。

例

たくさんの例(上記のmessage変数に基づいています)でそれを示しましょう。

正しい:追跡された関数内での間接参照

autorun(() => {
    console.log(message.title)
})
message.updateTitle("Bar")

これは期待どおりに反応します。.titleプロパティはautorunによって間接参照され、その後変更されたため、この変更が検出されます。

追跡された関数内でtrace()を呼び出すことで、MobXが何を追跡するかを確認できます。上記の関数の場合、次の出力になります。

import { trace } from "mobx"

const disposer = autorun(() => {
    console.log(message.title)
    trace()
})
// Outputs:
// [mobx.trace] 'Autorun@2' tracing enabled

message.updateTitle("Hello")
// Outputs:
// [mobx.trace] 'Autorun@2' is invalidated due to a change in: 'Message@1.title'
Hello

また、getDependencyTreeを使用して、内部依存関係(またはオブザーバー)ツリーを取得することもできます。

import { getDependencyTree } from "mobx"

// Prints the dependency tree of the reaction coupled to the disposer.
console.log(getDependencyTree(disposer))
// Outputs:
// { name: 'Autorun@2', dependencies: [ { name: 'Message@1.title' } ] }

不正解:observableでない参照の変更

autorun(() => {
    console.log(message.title)
})
message = new Message("Bar", { name: "Martijn" }, ["Felicia", "Marcus"])

これは反応しません。messageは変更されましたが、messageはobservableではなく、observableを参照する単なる変数ですが、変数(参照)自体はobservableではありません。

不正解:追跡された関数の外での間接参照

let title = message.title
autorun(() => {
    console.log(title)
})
message.updateMessage("Bar")

これは反応しません。message.titleはautorunの外で間接参照され、間接参照の瞬間のmessage.titleの値(文字列"Foo")のみが含まれています。titleはobservableではないため、autorunは反応しません。

正しい:追跡された関数内での間接参照

autorun(() => {
    console.log(message.author.name)
})

runInAction(() => {
    message.author.name = "Sara"
})
runInAction(() => {
    message.author = { name: "Joe" }
})

これは両方の変更に反応します。authorとauthor.nameの両方がドットで結ばれているため、MobXはこれらの参照を追跡できます。

actionの外で変更を行うには、ここでrunInActionを使用する必要があったことに注意してください。

不正解:observableなオブジェクトへのローカル参照を追跡せずに保存

const author = message.author
autorun(() => {
    console.log(author.name)
})

runInAction(() => {
    message.author.name = "Sara"
})
runInAction(() => {
    message.author = { name: "Joe" }
})

最初の変更は取得されます。message.authorとauthorは同じオブジェクトであり、.nameプロパティはautorunで間接参照されます。ただし、2番目の変更は取得されません。これは、message.authorの関係がautorunによって追跡されていないためです。Autorunは依然として「古い」authorを使用しています。

よくある落とし穴:console.log

autorun(() => {
    console.log(message)
})

// Won't trigger a re-run.
message.updateTitle("Hello world")

上記の例では、更新されたメッセージタイトルは、autorun内で使用されていないため、印刷されません。autorunは、observableではなく変数であるmessageにのみ依存しています。言い換えれば、MobXに関する限り、titleはautorunで使用されていません。

Webブラウザのデバッグツールでこれを使用すると、結局のところ、更新されたtitleの値を見つけることができるかもしれませんが、これは誤解を招きます。結局のところ、autorunは最初に呼び出されたときに1回実行されました。これは、console.logが非同期関数であり、オブジェクトが後でフォーマットされる場合に発生します。これは、デバッグツールバーでタイトルを追跡すると、更新された値を見つけることができることを意味します。ただし、autorunは更新を追跡しません。

これを機能させるには、常にイミュータブルなデータまたは防御的コピーをconsole.logに渡すようにする必要があります。したがって、以下の解決策はすべてmessage.titleの変更に反応します。

autorun(() => {
    console.log(message.title) // Clearly, the `.title` observable is used.
})

autorun(() => {
    console.log(mobx.toJS(message)) // toJS creates a deep clone, and thus will read the message.
})

autorun(() => {
    console.log({ ...message }) // Creates a shallow clone, also using `.title` in the process.
})

autorun(() => {
    console.log(JSON.stringify(message)) // Also reads the entire structure.
})

正:追跡関数での配列プロパティへのアクセス

autorun(() => {
    console.log(message.likes.length)
})
message.likes.push("Jennifer")

これは期待どおりに反応します。.lengthはプロパティとしてカウントされます。これは配列のあらゆる変更に反応することに注意してください。配列は、(observableオブジェクトやマップのように)インデックス/プロパティごとに追跡されるのではなく、全体として追跡されます。

不正:追跡関数での範囲外のインデックスへのアクセス

autorun(() => {
    console.log(message.likes[0])
})
message.likes.push("Jennifer")

配列インデックスはプロパティアクセスとしてカウントされるため、これは上記のサンプルデータで反応します。ただし、提供されたindex < lengthの場合のみです。MobXは、まだ存在しない配列インデックスを追跡しません。したがって、配列インデックスへのアクセスは常に.lengthチェックで保護してください。

正:追跡関数での配列関数へのアクセス

autorun(() => {
    console.log(message.likes.join(", "))
})
message.likes.push("Jennifer")

これは期待どおりに反応します。配列をミューテートしないすべての配列関数は、自動的に追跡されます。


autorun(() => {
    console.log(message.likes.join(", "))
})
message.likes[2] = "Jennifer"

これは期待どおりに反応します。すべての配列インデックスの代入が検出されますが、index <= lengthの場合のみです。

不正:observableを使用するが、そのプロパティにはアクセスしない

autorun(() => {
    message.likes
})
message.likes.push("Jennifer")

これは反応しません。単に、likes配列自体がautorunで使用されておらず、配列への参照のみが使用されているためです。対照的に、message.likes = ["Jennifer"]は取得されます。そのステートメントは配列を変更するのではなく、likesプロパティ自体を変更します。

正:まだ存在しないマップエントリの使用

const twitterUrls = observable.map({
    Joe: "twitter.com/joey"
})

autorun(() => {
    console.log(twitterUrls.get("Sara"))
})

runInAction(() => {
    twitterUrls.set("Sara", "twitter.com/horsejs")
})

これは反応します。Observableマップは、存在しない可能性のあるエントリの監視をサポートします。これは最初はundefinedを出力することに注意してください。最初にtwitterUrls.has("Sara")を使用することで、エントリの存在を確認できます。したがって、動的にキー設定されたコレクションのProxyサポートがない環境では、常にobservableマップを使用してください。Proxyサポートがある場合は、observableマップを使用することもできますが、プレーンオブジェクトを使用することもできます。

MobXは非同期的にアクセスされたデータを追跡しません

function upperCaseAuthorName(author) {
    const baseName = author.name
    return baseName.toUpperCase()
}
autorun(() => {
    console.log(upperCaseAuthorName(message.author))
})

runInAction(() => {
    message.author.name = "Chesterton"
})

これは反応します。author.nameがautorunに渡された関数自体によって逆参照されない場合でも、MobXはupperCaseAuthorNameで発生する逆参照を追跡します。なぜなら、autorunの実行中に発生するためです。


autorun(() => {
    setTimeout(() => console.log(message.likes.join(", ")), 10)
})

runInAction(() => {
    message.likes.push("Jennifer")
})

autorunの実行中にobservableがアクセスされなかったため、これは反応しません。非同期関数であるsetTimeout中にのみアクセスされました。

「非同期アクション」セクションも確認してください。

observableではないオブジェクトプロパティの使用

autorun(() => {
    console.log(message.author.age)
})

runInAction(() => {
    message.author.age = 10
})

これは、Proxyをサポートする環境でReactを実行している場合は反応します。これは、observableまたはobservable.objectで作成されたオブジェクトに対してのみ行われることに注意してください。クラスインスタンスの新しいプロパティは、自動的にobservableにはなりません。

Proxyサポートのない環境

これは反応しません。MobXはobservableプロパティのみを追跡でき、上記の「age」はobservableプロパティとして定義されていません。

ただし、MobXによって公開されているgetメソッドとsetメソッドを使用して、これを回避することが可能です。

import { get, set } from "mobx"

autorun(() => {
    console.log(get(message.author, "age"))
})
set(message.author, "age", 10)

[Proxyサポートなし]不正:まだ存在しないobservableオブジェクトプロパティの使用

autorun(() => {
    console.log(message.author.age)
})
extendObservable(message.author, {
    age: 10
})

これは反応しません。MobXは、追跡が開始されたときに存在しなかったobservableプロパティには反応しません。2つのステートメントが入れ替えられた場合、または他のobservableがautorunを再実行させた場合、autorunはageの追跡も開始します。

[Proxyサポートなし]正:オブジェクトの読み取り/書き込みにMobXユーティリティを使用する

Proxyサポートがない環境にいて、observableオブジェクトを動的コレクションとして使用したい場合は、MobXのgetおよびsetAPIを使用して処理できます。

以下も反応します

import { get, set, observable } from "mobx"

const twitterUrls = observable.object({
    Joe: "twitter.com/joey"
})

autorun(() => {
    console.log(get(twitterUrls, "Sara")) // `get` can track not yet existing properties.
})

runInAction(() => {
    set(twitterUrls, { Sara: "twitter.com/horsejs" })
})

詳細については、コレクションユーティリティAPIを確認してください。

要するに

MobXは、追跡された関数の実行中に読み取られる、既存のobservableなプロパティに反応します。

← データストアの定義サブクラス化 →
  • MobXは値ではなく、プロパティアクセスを追跡します
  • 例
MobX 🇺🇦
ドキュメント
MobXについてMobXの要点
コミュニティ
GitHubディスカッション(NEW)Stack Overflow
その他
スター