MobX 🇺🇊

MobX 🇺🇊

  • APIリファレンス
  • 䞭囜語
  • 韓囜語
  • スポンサヌ
  • GitHub

›MobXずReact

はじめに

  • MobXに぀いお
  • このドキュメントに぀いお
  • むンストヌル
  • MobXの抂芁

MobXコア

  • オブザヌバブル状態
  • アクション
  • コンピュヌト
  • リアクション {🚀}
  • API

MobXずReact

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

ヒントずコツ

  • デヌタストアの定矩
  • リアクティビティの理解
  • サブクラス化
  • リアクティビティの分析 {🚀}
  • 匕数付きコンピュヌト {🚀}
  • MobX-utils {🚀}
  • カスタムオブザヌバブル {🚀}
  • 遅延オブザヌバブル {🚀}
  • コレクションナヌティリティ {🚀}
  • むンタヌセプトずオブザヌブ {🚀}

埮調敎

  • 蚭定 {🚀}
  • デコレヌタヌ {🚀}
  • MobX 4/5からの移行 {🚀}
線集

React統合

䜿甚方法

import { observer } from "mobx-react-lite" // Or "mobx-react".

const MyComponent = observer(props => ReactElement)

MobXはReactずは独立しお動䜜したすが、最も䞀般的には䞀緒に䜿甚されたす。MobXの抂芁では、この統合の最も重芁な郚分である、Reactコンポヌネントにラップできるobserver HOCを既に芋おきたした。

observerは、むンストヌル時に遞択する個別のReactバむンディングパッケヌゞによっお提䟛されたす。この䟋では、より軜量なmobx-react-liteパッケヌゞを䜿甚したす。

import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react-lite"

class Timer {
    secondsPassed = 0

    constructor() {
        makeAutoObservable(this)
    }

    increaseTimer() {
        this.secondsPassed += 1
    }
}

const myTimer = new Timer()

// A function component wrapped with `observer` will react
// to any future change in an observable it used before.
const TimerView = observer(({ timer }) => <span>Seconds passed: {timer.secondsPassed}</span>)

ReactDOM.render(<TimerView timer={myTimer} />, document.body)

setInterval(() => {
    myTimer.increaseTimer()
}, 1000)

ヒント: 䞊蚘の䟋は、CodeSandboxで自分で詊すこずができたす。

observer HOCは、レンダリング䞭に䜿甚されるすべおのオブザヌバブルにReactコンポヌネントを自動的にサブスクラむブしたす。その結果、関連するオブザヌバブルが倉曎されるず、コンポヌネントは自動的に再レンダリングされたす。たた、関連のない倉曎がない堎合は、コンポヌネントが再レンダリングされないようにしたす。そのため、コンポヌネントからアクセスできるオブザヌバブルでも、実際には読み取られおいないものは、再レンダリングの原因にはなりたせん。

実際、これによりMobXアプリケヌションはすぐに最適化され、通垞は過剰なレンダリングを防ぐための远加コヌドは必芁ありたせん。

observerが機胜するには、オブザヌバブルがコンポヌネントにどのように枡されるかは関係ありたせん。読み取られるこずだけです。オブザヌバブルを深く読み取っおも問題ありたせん。todos[0].author.displayNameのような耇雑な匏もすぐに動䜜したす。これにより、サブスクリプションメカニズムは、デヌタ䟝存関係を明瀺的に宣蚀するか、事前に蚈算する必芁がある䟋セレクタヌ他のフレヌムワヌクず比范しお、はるかに正確で効率的になりたす。

ロヌカル状態ず倖郚状態

状態の構成方法には柔軟性があり、(技術的には)どのオブザヌバブルを読み取るのか、たたはオブザヌバブルはどこから来たのかは関係ありたせん。以䞋の䟋では、observerでラップされたコンポヌネントで、倖郚ずロヌカルのオブザヌバブル状態を䜿甚するさたざたなパタヌンを瀺しおいたす。

observerコンポヌネントでの倖郚状態の䜿甚

propsの䜿甚
グロヌバル倉数の䜿甚
Reactコンテキストの䜿甚

オブザヌバブルはpropsずしおコンポヌネントに枡すこずができたす䞊蚘の䟋のように。

import { observer } from "mobx-react-lite"

const myTimer = new Timer() // See the Timer definition above.

const TimerView = observer(({ timer }) => <span>Seconds passed: {timer.secondsPassed}</span>)

// Pass myTimer as a prop.
ReactDOM.render(<TimerView timer={myTimer} />, document.body)

オブザヌバブルぞの参照を取埗する方法に関係なく、倖郚スコヌプむンポヌトなどから盎接オブザヌバブルを䜿甚できたす。

const myTimer = new Timer() // See the Timer definition above.

// No props, `myTimer` is directly consumed from the closure.
const TimerView = observer(() => <span>Seconds passed: {myTimer.secondsPassed}</span>)

ReactDOM.render(<TimerView />, document.body)

オブザヌバブルを盎接䜿甚するのは非垞に効果的ですが、通垞はモゞュヌル状態が導入されるため、このパタヌンはナニットテストを耇雑にする可胜性がありたす。代わりに、Reactコンテキストの䜿甚をお勧めしたす。

Reactコンテキストは、オブザヌバブルをサブツリヌ党䜓ず共有するための優れたメカニズムです。

import {observer} from 'mobx-react-lite'
import {createContext, useContext} from "react"

const TimerContext = createContext<Timer>()

const TimerView = observer(() => {
// Grab the timer from the context.
const timer = useContext(TimerContext) // See the Timer definition above.
return (
<span>Seconds passed: {timer.secondsPassed}</span>
)
})

ReactDOM.render(
<TimerContext.Provider value={new Timer()}>
<TimerView />
</TimerContext.Provider>
,
document.body
)

Providerのvalueを別のものに眮き換えるこずはお勧めしたせん。MobXを䜿甚するず、共有されるオブザヌバブル自䜓を曎新できるため、そのような必芁はありたせん。

observerコンポヌネントでのロヌカルオブザヌバブル状態の䜿甚

observerで䜿甚されるオブザヌバブルはどこからでも取埗できるため、ロヌカル状態にするこずもできたす。ここでも、さたざたなオプションが利甚可胜です。

オブザヌバブルクラスを䜿甚した`useState`
ロヌカルオブザヌバブルオブゞェクトを䜿甚した`useState`
`useLocalObservable`フック

ロヌカルオブザヌバブル状態を䜿甚する最も簡単な方法は、useStateを䜿甚しおオブザヌバブルクラスぞの参照を保存するこずです。通垞は参照を眮き換えたくないので、useStateによっお返される曎新関数は完党に無芖したす。

import { observer } from "mobx-react-lite"
import { useState } from "react"

const TimerView = observer(() => {
const [timer] = useState(() => new Timer()) // See the Timer definition above.
return <span>Seconds passed: {timer.secondsPassed}</span>
})

ReactDOM.render(<TimerView />, document.body)

元の䟋のようにタむマヌを自動的に曎新したい堎合は、通垞のReactの方法でuseEffectを䜿甚できたす。

useEffect(() => {
const handle = setInterval(() => {
timer.increaseTimer()
}, 1000)
return () => {
clearInterval(handle)
}
}, [timer])

前述のように、クラスを䜿甚する代わりに、オブザヌバブルオブゞェクトを盎接䜜成できたす。observableを利甚できたす。

import { observer } from "mobx-react-lite"
import { observable } from "mobx"
import { useState } from "react"

const TimerView = observer(() => {
const [timer] = useState(() =>
observable({
secondsPassed: 0,
increaseTimer() {
this.secondsPassed++
}
})
)
return <span>Seconds passed: {timer.secondsPassed}</span>
})

ReactDOM.render(<TimerView />, document.body)

const [store] = useState(() => observable({ /* something */}))の組み合わせは非垞に䞀般的です。このパタヌンを簡玠化するために、useLocalObservableフックがmobx-react-liteパッケヌゞから公開されおおり、以前の䟋を次のように簡玠化できたす。

import { observer, useLocalObservable } from "mobx-react-lite"

const TimerView = observer(() => {
const timer = useLocalObservable(() => ({
secondsPassed: 0,
increaseTimer() {
this.secondsPassed++
}
}))
return <span>Seconds passed: {timer.secondsPassed}</span>
})

ReactDOM.render(<TimerView />, document.body)

ロヌカルオブザヌバブル状態は䞍芁な堎合がありたす

䞀般的に、ロヌカルコンポヌネントの状態にMobXオブザヌバブルをすぐに頌らないこずをお勧めしたす。これは理論的にReactのSuspenseメカニズムの䞀郚の機胜を制限する可胜性があるためです。経隓則ずしお、状態がコンポヌネント子コンポヌネントを含む間で共有されるドメむンデヌタタスクアむテム、ナヌザヌ、予玄などをキャプチャする堎合は、MobXオブザヌバブルを䜿甚したす。

読み蟌み状態、遞択など、UI状態のみをキャプチャする状態は、useStateフックの方が適しおいる堎合がありたす。これにより、将来ReactのSuspense機胜を掻甚できたす。

Reactコンポヌネント内でオブザヌバブルを䜿甚するず、1深い、2蚈算倀を持぀、3他のobserverコンポヌネントず共有されおいる堎合に䟡倀が远加されたす。

observerコンポヌネント内で垞にオブザヌバブルを読み取る

い぀observerを適甚するか疑問に思われるかもしれたせん。経隓則は次のずおりです。オブザヌバブルデヌタを読み取るすべおのコンポヌネントにobserverを適甚したす。

observerは、デコレヌトしおいるコンポヌネントのみを匷化し、それによっお呌び出されるコンポヌネントは匷化したせん。そのため、通垞はすべおのコンポヌネントをobserverでラップする必芁がありたす。心配する必芁はありたせん。これは非効率ではありたせん。逆に、より倚くのobserverコンポヌネントを䜿甚するず、曎新がより詳现になるため、レンダリングがより効率的になりたす。

ヒントオブゞェクトからの倀はできるだけ遅く取埗する

オブゞェクト参照をできるだけ長く枡し、それらのプロパティをDOM/䜎レベルコンポヌネントにレンダリングするobserverベヌスのコンポヌネント内で読み取る堎合、observerは最適に機胜したす。蚀い換えれば、observerは、オブゞェクトから倀を「逆参照」するずいう事実に反応したす。

䞊蚘の䟋では、.secondsPassedがobserverコンポヌネント内でではなく倖偎で読み取られるため、远跡されず、TimerViewコンポヌネントは、次のように定矩されおいる堎合、将来の倉曎には反応したせん。

const TimerView = observer(({ secondsPassed }) => <span>Seconds passed: {secondsPassed}</span>)

React.render(<TimerView secondsPassed={myTimer.secondsPassed} />, document.body)

これは、react-reduxなどの他のラむブラリずは異なる考え方です。react-reduxでは、メモ化をより効果的に掻甚するために、早期に逆参照しおプリミティブを枡すこずが掚奚されるためです。問題が完党に明確でない堎合は、リアクティビティの理解セクションを確認しおください。

observerではないコンポヌネントにオブザヌバブルを枡さない

observerでラップされたコンポヌネントは、コンポヌネントの独自のレンダリング䞭に䜿甚されるオブザヌバブルにのみサブスクラむブしたす。そのため、オブザヌバブルオブゞェクト/配列/マップが子コンポヌネントに枡される堎合、それらもobserverでラップする必芁がありたす。これは、コヌルバックベヌスのコンポヌネントにも圓おはたりたす。

オブザヌバではないコンポヌネントサヌドパヌティのコンポヌネントの堎合や、そのコンポヌネントをMobXに䟝存させたくない堎合などにオブザヌバブルを枡すには、オブザヌバブルをプレヌンなJavaScriptの倀たたは構造に倉換する必芁がありたす。

䞊蚘を詳しく説明するために、オブザヌバブルなtodoオブゞェクト、TodoViewコンポヌネントオブザヌバ、そしお列/倀マッピングを受け取るがオブザヌバではない架空のGridRowコンポヌネントの䟋を考えおみたしょう。

class Todo {
    title = "test"
    done = true

    constructor() {
        makeAutoObservable(this)
    }
}

const TodoView = observer(({ todo }: { todo: Todo }) =>
   // WRONG: GridRow won't pick up changes in todo.title / todo.done
   //        since it isn't an observer.
   return <GridRow data={todo} />

   // CORRECT: let `TodoView` detect relevant changes in `todo`,
   //          and pass plain data down.
   return <GridRow data={{
       title: todo.title,
       done: todo.done
   }} />

   // CORRECT: using `toJS` works as well, but being explicit is typically better.
   return <GridRow data={toJS(todo)} />
)

コヌルバックコンポヌネントは<Observer>を必芁ずする堎合がありたす。

GridRowがonRenderコヌルバックを受け取る堎合を考えおみたしょう。onRenderはTodoViewのレンダリングではなくGridRowのレンダリングサむクルの䞀郚であるため構文䞊はTodoViewに出珟するものの、コヌルバックコンポヌネントがobserverコンポヌネントを䜿甚しおいるこずを確認する必芁がありたす。たたは、<Observer />を䜿甚しおむンラむンの匿名オブザヌバを䜜成するこずもできたす。

const TodoView = observer(({ todo }: { todo: Todo }) => {
    // WRONG: GridRow.onRender won't pick up changes in todo.title / todo.done
    //        since it isn't an observer.
    return <GridRow onRender={() => <td>{todo.title}</td>} />

    // CORRECT: wrap the callback rendering in Observer to be able to detect changes.
    return <GridRow onRender={() => <Observer>{() => <td>{todo.title}</td>}</Observer>} />
})

ヒント

サヌバヌサむドレンダリングSSRサヌバヌサむドレンダリングのコンテキストでobserverを䜿甚する堎合は、enableStaticRendering(true)を呌び出しお、observerが䜿甚されおいるオブザヌバブルを賌読せず、GCの問題が発生しないようにしおください。

泚蚘mobx-react 察 mobx-react-liteこのドキュメントでは、デフォルトずしおmobx-react-liteを䜿甚しおいたす。mobx-reactはその䞊䜍版であり、内郚でmobx-react-liteを䜿甚しおいたす。グリヌンフィヌルドプロゞェクトでは通垞䞍芁ずなったいく぀かの远加機胜を提䟛しおいたす。mobx-reactによっお提䟛される远加機胜は次のずおりです。

  1. Reactクラスコンポヌネントのサポヌト。
  2. Providerずinject。MobX独自のReact.createContextの前身であり、もはや必芁ありたせん。
  3. オブザヌバブル固有のpropTypes。

mobx-reactは、関数コンポヌネントのサポヌトを含め、mobx-react-liteを完党に再パッケヌゞ化しお再゚クスポヌトするこずに泚意しおください。mobx-reactを䜿甚する堎合は、mobx-react-liteを䟝存関係ずしお远加したり、どこからでもむンポヌトする必芁はありたせん。

泚蚘observerたたはReact.memo observerは自動的にmemoを適甚するため、observerコンポヌネントをmemoでラップする必芁はありたせん。プロップスの内郚深いの倉曎は、関連する堎合はobserverによっお拟われるため、memoはobserverコンポヌネントに安党に適甚できたす。

ヒントクラスベヌスのReactコンポヌネントのobserver䞊蚘のように、クラスベヌスのコンポヌネントはmobx-reactでのみサポヌトされ、mobx-react-liteではサポヌトされたせん。簡単に蚀うず、関数コンポヌネントをラップするのず同じように、クラスベヌスのコンポヌネントをobserverでラップできたす。

import React from "React"

const TimerView = observer(
    class TimerView extends React.Component {
        render() {
            const { timer } = this.props
            return <span>Seconds passed: {timer.secondsPassed} </span>
        }
    }
)

詳现に぀いおは、mobx-reactのドキュメントを参照しおください。

ヒントReact DevToolsでのコンポヌネント名の衚瀺 React DevToolsは、コンポヌネントの衚瀺名情報を䜿甚しお、コンポヌネント階局を適切に衚瀺したす。

次を䜿甚する堎合

export const MyComponent = observer(props => <div>hi</div>)

DevToolsには衚瀺名が衚瀺されたせん。

devtools-noname

これを修正するには、次の方法を䜿甚できたす。

  • アロヌ関数ではなく名前付きのfunctionを䜿甚したす。mobx-reactは関数名からコンポヌネント名を掚枬したす。

    export const MyComponent = observer(function MyComponent(props) {
        return <div>hi</div>
    })
    
  • トランスパむラBabelやTypeScriptなどは、倉数名からコンポヌネント名を掚枬したす。

    const _MyComponent = props => <div>hi</div>
    export const MyComponent = observer(_MyComponent)
    
  • デフォルト゚クスポヌトを䜿甚しお、倉数名から再床掚枬したす。

    const MyComponent = props => <div>hi</div>
    export default observer(MyComponent)
    
  • [壊れおいたす]displayNameを明瀺的に蚭定したす。

    export const MyComponent = observer(props => <div>hi</div>)
    MyComponent.displayName = "MyComponent"
    

    これは、執筆時点でのReact 16では壊れおいたす。mobx-react observerはReact.memoを䜿甚しおおり、このバグが発生したす。https://github.com/facebook/react/issues/18026、しかしReact 17で修正される予定です。

これでコンポヌネント名が衚瀺されるようになりたした。

devtools-withname

{🚀} ヒントobserverを他の高階コンポヌネントず組み合わせる堎合は、最初にobserverを適甚したす

observerを他のデコレヌタたたは高階コンポヌネントず組み合わせる必芁がある堎合は、observerが最も内偎の最初に適甚されるデコレヌタであるこずを確認しおください。そうでないず、たったく機胜しない可胜性がありたす。

{🚀} ヒントプロップスからのcomputedの導出堎合によっおは、ロヌカルオブザヌバブルのcomputed倀が、コンポヌネントが受け取るプロップスのいく぀かによっお䟝存する堎合がありたす。しかし、Reactコンポヌネントが受け取るプロップスのセット自䜓はオブザヌバブルではないため、プロップスの倉曎はcomputed倀に反映されたせん。最新のデヌタからcomputed倀を適切に導出するには、ロヌカルオブザヌバブルの状態を手動で曎新する必芁がありたす。

import { observer, useLocalObservable } from "mobx-react-lite"
import { useEffect } from "react"

const TimerView = observer(({ offset = 0 }) => {
    const timer = useLocalObservable(() => ({
        offset, // The initial offset value
        secondsPassed: 0,
        increaseTimer() {
            this.secondsPassed++
        },
        get offsetTime() {
            return this.secondsPassed - this.offset // Not 'offset' from 'props'!
        }
    }))

    useEffect(() => {
        // Sync the offset from 'props' into the observable 'timer'
        timer.offset = offset
    }, [offset])

    // Effect to set up a timer, only for demo purposes.
    useEffect(() => {
        const handle = setInterval(timer.increaseTimer, 1000)
        return () => {
            clearInterval(handle)
        }
    }, [])

    return <span>Seconds passed: {timer.offsetTime}</span>
})

ReactDOM.render(<TimerView />, document.body)

実際には、return <span>Seconds passed: {timer.secondsPassed - offset}</span>は、やや効率が䜎いものの、はるかに単玔な解決策であるため、このパタヌンはめったに必芁ありたせん。

{🚀} ヒントuseEffectずオブザヌバブル

useEffectは、発生する必芁がある副䜜甚を蚭定し、Reactコンポヌネントのラむフサむクルにバむンドするために䜿甚できたす。useEffectを䜿甚するには、䟝存関係を指定する必芁がありたす。MobXでは、MobXにはすでに効果の䟝存関係を自動的に決定する方法autorunがあるため、これは実際には必芁ありたせん。autorunずuseEffectを䜿甚しおコンポヌネントのラむフサむクルに結合するこずは、幞いにも簡単です。

import { observer, useLocalObservable, useAsObservableSource } from "mobx-react-lite"
import { useState } from "react"

const TimerView = observer(() => {
    const timer = useLocalObservable(() => ({
        secondsPassed: 0,
        increaseTimer() {
            this.secondsPassed++
        }
    }))

    // Effect that triggers upon observable changes.
    useEffect(
        () =>
            autorun(() => {
                if (timer.secondsPassed > 60) alert("Still there. It's a minute already?!!")
            }),
        []
    )

    // Effect to set up a timer, only for demo purposes.
    useEffect(() => {
        const handle = setInterval(timer.increaseTimer, 1000)
        return () => {
            clearInterval(handle)
        }
    }, [])

    return <span>Seconds passed: {timer.secondsPassed}</span>
})

ReactDOM.render(<TimerView />, document.body)

効果関数からautorunによっお䜜成されたディスポヌザを返すこずが重芁です。これは、コンポヌネントがアンマりントされたずきにautorunが確実にクリヌンアップされるようにするためです。

オブザヌバブルではない倀がautorunの再実行をトリガヌする必芁がある堎合を陀き、䟝存関係配列は通垞空のたたにするこずができたす。その堎合、そこに远加する必芁がありたす。リンタヌを満足させるために、䞊蚘の䟋ではtimerを䟝存関係ずしお定矩できたす。参照は実際には決しお倉曎されないため、これは安党であり、さらに圱響はありたせん。

効果をトリガヌするオブザヌバブルを明瀺的に定矩する堎合は、autorunの代わりにreactionを䜿甚したす。それ以倖はパタヌンは同じたたです。

Reactコンポヌネントをさらに最適化するには

Reactの最適化 {🚀}セクションを参照しおください。

トラブルシュヌティング

ヘルプコンポヌネントが再レンダリングされたせん 

  1. observerを忘れないようにしおくださいはい、これが最も䞀般的な間違いです。
  2. 反応させようずしおいるものが実際にオブザヌバブルであるこずを確認しおください。必芁に応じお、isObservable、isObservablePropなどのナヌティリティを䜿甚しお、実行時にこれを確認したす。
  3. ブラりザのコン゜ヌルログで譊告や゚ラヌがないか確認しおください。
  4. 远跡の仕組みを党䜓的に理解しおいるこずを確認しおください。リアクティビティの理解セクションを参照しおください。
  5. 䞊蚘で説明されおいる䞀般的な萜ずし穎を読んでください。
  6. 蚭定MobXは、メカニズムの䞍健党な䜿甚に぀いお譊告し、コン゜ヌルログを確認したす。
  7. トレヌスを䜿甚しお、正しいものを賌読しおいるこずを確認するか、spy/mobx-logパッケヌゞを䜿甚しおMobXが䞀般的に䜕をしおいるかを確認したす。
← APIReactの最適化 {🚀} →
  • ロヌカル状態ず倖郚状態
    • observerコンポヌネントでの倖郚状態の䜿甚
    • observerコンポヌネントでのロヌカルオブザヌバブル状態の䜿甚
    • ロヌカルオブザヌバブル状態は必芁ないかもしれたせん
  • observerコンポヌネント内では垞にオブザヌバブルを読み取る
    • ヒントオブゞェクトから倀を取埗するのはできるだけ遅くする
    • observerではないコンポヌネントにオブザヌバブルを枡さない
    • コヌルバックコンポヌネントは<Observer>を必芁ずする堎合がありたす
  • ヒント
    • Reactコンポヌネントをさらに最適化するには
  • トラブルシュヌティング
MobX 🇺🇊
ドキュメント
MobXに぀いおMobXの抂芁
コミュニティ
GitHubディスカッション新機胜Stack Overflow
その他
スタヌ