Help us understand the problem. What is going on with this article?

MobXの observable と action について

More than 3 years have passed since last update.

先日の投稿に続き第3弾です。前回はMobXAPIの話を差し置いて、いきなりReactMobXの設計論になってしまいました。本稿では個人的にMobXのちょっと分かり辛い・気をつけないといけないなと感じた、基本の部分を綴っています。ゆるい感じでピックアップしながら、気ままに記事を書いていこうと思います。

【DevTools】

もうMobXのコードを書いてみた方で、DevToolsを導入していない方は是非いれてみてください。こちらのページで<DevTools / >をマウントして導入する方法が紹介されていますが、ChromeExtensionをいれておけば、マウント無しで同じ機能が利用出来る様になります。本稿の記事を読み解くにあたり、役にたつはずです。

observable型について

observable値には、JSプリミティブ、参照、プレーンオブジェクト、クラスインスタンス、配列、マップなどがあります。@observableは、常に最適な observable型を作成しようとします。なので、何となしで @observableな値を作ってしまうと危険です。

observable型は、階層をどこまで辿って追跡可能な値とするのか、modifiers で指定することが出来ます。modifiersには「ref」「shallow」「deep」があり、何も指定しなければ、常に.deepとなります。

store.js
class Store {
  @observable.ref      collectionRef = []
  @observable.shallow  collectionShallow = []
  @observable/*.deep*/ collectionDeep = []
}

.refは最も浅い追跡をします。下記の様な挙動です。Objectの参照が変わることをフックに、observerに伝達させます。

store.js
class Store {
  @observable.ref collection = []
  @action editCollection = () => {
    // 参照が変わるので、伝達する(新しい配列を参照)
    this.collection    = [{ name: 'myName' }]
    // 参照はそのままなので、伝達しない
    this.collection[0] = { name: 'myName' }
  }
}

.shallowは一階層目まで追跡、変更が伝達されます。参照の変更においても伝達されます。

store.js
class Store {
  @observable.shallow user = {
    name: 'myName',
    friends: [{ name: 'firendName' }]
  }
  @action editUser = () => {
    // 1階層目なので、伝達する
    this.user.name = 'newName'
    // 2階層目より深いので、伝達しない
    this.user.friends[0].name = 'newFriendName'
    // 参照が変わっているので、伝達する
    this.user = { name: 'myName', friends: [{ name: 'newFriendName' }]}
  }
}

.deepはどこまで深くなっても追跡、変更が伝達されます。デフォルトの挙動がこれです。気をつけなければいけないのが、サンプルコードの下部、新しいObjectを参照しているところです。

store.js
class Store {
  @observable user = {
    name: 'myName',
    friends: [{ name: 'firendName' }]
  }
  @action editUser = () => {
    // 伝達する(1度だけ)
    this.user.friends[0].name = 'newFriendName'
    // 伝達する(3度も)
    this.user = { name: 'myName', friends: [{ name: 'newFriendName' }]}
  }
}

deepで追跡しているため、以下の3点が observable値とみなされ、
observerへの伝達が3度も走ることになります。これはthis.userを参照している Reactコンポーネントで、3度リレンダリングされることを意味します。

  • this.user.name
  • this.user.friends
  • this.user.friends[0]

この点をきちんと把握しておかないと、知らず知らずのうちにパフォーマンスが劣化してしまいます。APIから巨大なjson配列を取得して @observable = [] などに代入すると、とんでもなく遅くなります。こういったものには @observable.ref = [] の様に、要件に応じた modifier を付与しましょう。

また上記は、伝達するかしないかの話であって、内容は変化しています。別コンポーネントの変化によって再レンダリングされた時に変化するので、その挙動に少し戸惑うかもしれません。

actionの存在意義

カッチリ書きたい人向け、と考えて良さそうです。
とりあえず useStrict(true) を宣言してみましょう。

main.js
import { useStrict } from 'mobx'
useStrict(true)

@action でデコレートされていない関数以外から observable値を変更しようとすると、いろいろ怒られます。useStrict(true) 宣言も @action デコレータの使用も、optional とのことですが、堅牢に作りたい場合やログを追える様になるので、個人的にはactionも、strictモードも使う派です。

Error: [mobx] Invariant failed: Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. 
  • action を付与することで、stateを変更する関数であることを明示的に宣言する。
  • observable値を変更したり副作用を伴う関数は action 使用が推奨される。
  • DevToolsでログ出力される様になる。
  • strictモードを有効にしている場合、action を経由しないobservable値の変更は怒られる。
  • コールバックなどの副作用から、observable値を変更しようとする場合、runInAction 関数でラップしなければ怒られる。
  • runInAction 関数は第一引数にDevToolsのログを設定できる。

ちなみに、@actionデコレータも下記の様にログ用の文字列を指定することが出来ます。日本語もござれ。

someStore.js
@action('値を1追加') increment = () => this.count++

image

Takepepe
Web Application Developer. interested in TypeScript AST.
http://design.dena.com/
dena_coltd
    Delight and Impact the World
https://dena.com/jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした