mutations.md 7.5 KB

ミューテーション

Vuex のミューテーションは本質的にイベントです。各ミューテーションは名前ハンドラを持ちます。ハンドラ関数は常に Vuex の state を第1引数として取得します:

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    INCREMENT (state) {
      // 状態の変更
      state.count++
    }
  }
})

ミューテーションの名前に全て大文字を使用するのは、容易に通常の関数と区別できるようにするための規約です。

直接ミューテーションハンドラを呼び出すことはできません。この mutations オプションは、どちらかいうと"INCREMENT イベントがディスパッチされるとき、このハンドラが呼ばれる"といったイベント登録のようなものです。ミューテーションハンドラを起動するためには、ミューテーションイベントをディスパッチする必要があります:

store.dispatch('INCREMENT')

引数によるディスパッチ

引数を渡すことも可能です:

// ...
mutations: {
  INCREMENT (state, n) {
    state.count += n
  }
}
store.dispatch('INCREMENT', 10)

ここでの 10state に続く第2引数としてミューテーションハンドラに渡されます。さらに追加される引数についても同様です。これらの引数は、特定のミューテーションに対するペイロードと呼びます。

オブジェクトスタイルのディスパッチ

またオブジェクトを利用してミューテーションをディスパッチすることもできます:

store.dispatch({
  type: 'INCREMENT',
  payload: 10
})

オブジェクトスタイルを利用するとき、全ての引数をディスパッチされるオブジェクトのプロパティとして含めなければいけないことに注意してください。全体のオブジェクトは、ミューテーションハンドラの第2引数として渡されます。

mutations: {
  INCREMENT (state, mutation) {
    state.count += mutation.payload
  }
}

サイレントディスパッチ

場合によっては、プラグインに状態の変化を記録して欲しくないこともあるでしょう。あるいは、短い間隔、ポーリングでのストアへの複数のディスパッチも、常に追跡する必要はないでしょう。これらの状況では、ミューテーションを沈黙( silence )させることが適切と考えることができます。

注意: サイレントディスパッチは可能な限り避けるべきです。サイレントミューテーションは、開発ツールの全ての状態の変更を追跡するという規約を壊します。絶対に必要だという状況で控えめに使用してください。

/**
 * 例: プログレス アクション
 * 追跡する必要がない変更を頻繁に送ります
 **/
export function start(store, options = {}) {
  let timer = setInterval(() => {
    store.dispatch({
      type: INCREMENT,
      silent: true,
      payload: {
        amount: 1,
      },
    });
    if (store.state.progress === 100) {
      clearInterval(timer);
    }
  }, 10);
}

Vue のリアクティブなルールに則ったミューテーション

Vuex ストアのステートは Vue によってリアクティブになっているので、ステートを変更すると、ステートを監視している Vue コンポーネントは自動的に更新されます。これは、Vuex のミューテーションは、通常の Vue で動作させているときと同じリアクティブな警告の対象となることを意味します:

  1. 前もって、全ての必要なフィールドによって、ストアの初期状態を初期化することを好みます

  2. 新しいプロパティをオブジェクトに追加するとき、以下のいずれかが必要です:

    • Vue.set(obj, 'newProp', 123) を使用する。あるいは

    • 全く新しいオブジェクトで既存のオブジェクトを置き換える。例えば、stage-2 の object spread syntax を使用して、以下のように書くことができます:

      state.obj = { ...state.obj, newProp: 123 }
      

ミューテーション名に定数を使用する

ミューテーション名には定数を使用することが一般的です。これは、コードに対してリントツールのようなツールを利用できるという利点があり、また、単一ファイルに全ての定数を設定することで、共同で作業する人にアプリケーション全体で何のミューテーションが可能であるか一目見ただけで理解できるようにします:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  actions: { ... },
  mutations: {
    // 定数を関数名として使用できる ES2015 の算出プロパティ (computed property) 名機能を使用できます
    [SOME_MUTATION] (state) {
      // 変異するステート
    }
  }
})

定数を使用するかどうか大抵は好みであり、多くの開発者による大規模アプリケーションで役に立ちますが、もしお気に召さなければ、使用しなくても構いません。これは完全にオプションです。

ミューテーションは同期的でなければならない

ひとつの重要なルールを覚えておきましょう。それはミューテーションハンドラ関数は同期的でなければならないということです。なぜか? 次の例で考えてみましょう:

mutations: {
  SOME_MUTATION (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

いま、ミューテーションのログを見て、アプリケーションのデバッグを行っていることを想像してください。全てのミューテーションはログに記録されていて、ミューテーションの前後の状態のスナップショットを比較することが可能です。しかし、例のミューテーション内の非同期コールバックは、それを不可能にします: そのコールバックは、ミューテーションがディスパッチされたときにまだ呼ばれません。そして、コールバックが実際いつ呼ばれるかは分かりません。いかなる状態変更でも、コールバック内で起きる場合は本質的に追跡不可能です。

アクションに続けて

状態変更を非同期に組み合わせることは、プログラムの動きを予測することを非常に困難にするかもしれません。例えば、状態を変更する非同期コールバックを持った2つのメソッドを両方呼び出しとき、どうやってそれらが呼び出されたか、あるいは先に呼び出されたかのはどちらかなのか知ればよいのでしょう? 状態変更と非同期の2つの概念を分離したいという理由は、はっきりしています。 Vuex では、全ての状態変更は同期的におこなうという作法になっています。全ての非同期命令は アクション の内部でおこなうことになるでしょう。