From c29078af72498ec5007e3239083b4d1135e3cefb Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Sat, 29 Oct 2016 12:58:28 +0900 Subject: [PATCH 01/80] Starting japanese translation. From 0a1d6b69d9a27c61751df3b795d23a1aa6bdbcb2 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Sat, 29 Oct 2016 13:38:56 +0900 Subject: [PATCH 02/80] Move intro.md from v1.0 --- docs/ja/intro.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/ja/intro.md diff --git a/docs/ja/intro.md b/docs/ja/intro.md new file mode 100644 index 000000000..8525800f1 --- /dev/null +++ b/docs/ja/intro.md @@ -0,0 +1,13 @@ +# Vuex とは何か + +Vuex は Vue.js アプリケーションで状態を一元的に管理するためのアプリケーションアーキテクチャです。[Flux](https://facebook.github.io/flux/) や [Redux](https://github.com/rackt/redux) にインスパイアされていますが、それよりも単純化された概念を持ち、 Vue.js のリアクティブシステムの長所を生かすために、特別に設計された実装になっています。 + +## なぜこれを必要とするのですか? + +あなたのアプリケーションが十分に単純であるなら、おそらく Vuex は必要ないでしょう。早まって Vuex を適用しないようにしてください。しかし、中〜大規模な SPA を構築することになったら、Vue コンポーネントの外側の構造をより良くする方法を考える適切なタイミングです。そこで Vuex の出番です。 + +Vue.js を単体で使用するとき、私達はコンポーネントの"内部"に状態を格納しようとする傾向があります。つまり、各コンポーネントは、アプリケーションの状態の一部を所有しており、結果として状態が様々な場所に散らばっています。しかし、時には一部の状態を、複数のコンポーネントの間で共有したいこともあるでしょう。このようなケースで、カスタムイベントシステムを利用して、あるコンポーネントがいくつかの状態を他のコンポーネントに"送信"する例がよく見られます。このパターンは、大規模なコンポーネントツリーでは、内部のイベントフローが瞬く間に複雑になり、正しく動作しない場合に原因を調査するのが困難になるという問題があります。 + +大規模アプリケーションで状態の共有をより良く対処するために、**コンポーネント内部の状態**と**アプリケーションレベルの状態**を区別する必要があります。アプリケーションレベルの状態は特定のコンポーネントに属していませんが、私達のコンポーネントはリアクティブな DOM 更新のためにまだそれを監視することができます。ひとつの場所で一元的に状態を管理することによって、複数のコンポーネントに影響を与える状態すべてがそこに属している必要があり、もはやイベントをあちこちに渡す必要はありません。加えて、全ての状態の変更の記録と検査がおこなわれることで、状態が変化する過程の理解を容易にし、さらにタイムトラベルデバッグのような興味深い機能も実装できます。 + +Vuex は状態を管理するロジックの分割について、他にも様々な場所で一定の制約を強いてきますが、それでも実際のコード構造に対して十分な柔軟性を持っています。 From f6436b90c9abee21129da5647115a9fb4ac40948 Mon Sep 17 00:00:00 2001 From: tady Date: Mon, 31 Oct 2016 09:01:44 +0900 Subject: [PATCH 03/80] Installation.md in Japanese --- docs/ja/installation.md | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/ja/installation.md diff --git a/docs/ja/installation.md b/docs/ja/installation.md new file mode 100644 index 000000000..01fff6ea0 --- /dev/null +++ b/docs/ja/installation.md @@ -0,0 +1,42 @@ +# Installation + +### 直接のDownload / CDN + +[https://unpkg.com/vuex](https://unpkg.com/vuex) + +[Unpkg.com](https://unpkg.com) で NPM ベースの CDN リンクが提供されています。上記リンクは常に NPM の最新のリリースを指します。`https://unpkg.com/vuex@2.0.0` のような URL によって特定のバージョン/タグを利用することもできます。 + +Vue のあとで `vuex` を取り込むと自動的に Vuex が導入されます: + +``` html + + +``` + +### NPM + +``` bash +npm install vuex +``` + +モジュールシステムで利用される場合、 `Vue.use()` によって Vuex を明示的に導入する必要があります: + +``` js +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) +``` + +グローバルなスクリプトタグを利用する場合にはこのようにする必要はありません。 + +### 開発版 Build + +最新の開発版ビルドを利用したい場合には、 Github から直接クローンし `vuex` を自身でビルドする必要があります。 + +``` bash +git clone https://github.com/vuejs/vuex.git node_modules/vuex +cd node_modules/vuex +npm install +npm run build +``` From f311ca0acec89d1d39daba7d1e260cb523970854 Mon Sep 17 00:00:00 2001 From: tady Date: Mon, 31 Oct 2016 09:08:07 +0900 Subject: [PATCH 04/80] translate headers --- docs/ja/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ja/installation.md b/docs/ja/installation.md index 01fff6ea0..cde4de290 100644 --- a/docs/ja/installation.md +++ b/docs/ja/installation.md @@ -1,4 +1,4 @@ -# Installation +# 導入方法 ### 直接のDownload / CDN @@ -30,7 +30,7 @@ Vue.use(Vuex) グローバルなスクリプトタグを利用する場合にはこのようにする必要はありません。 -### 開発版 Build +### 開発版ビルド 最新の開発版ビルドを利用したい場合には、 Github から直接クローンし `vuex` を自身でビルドする必要があります。 From 084e85e60d2ce520b0d1fa6a03cf1d41ec29201c Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 21:18:38 +0900 Subject: [PATCH 05/80] Copy docs/en/getting-started.md from 1.0 --- docs/ja/getting-started.md | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/ja/getting-started.md diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md new file mode 100644 index 000000000..788bf6f35 --- /dev/null +++ b/docs/ja/getting-started.md @@ -0,0 +1,53 @@ +# Getting Started + +At the center of every Vuex application is the **store**. A "store" is basically a container that holds your application **state**. There are two things that makes a Vuex store different from a plain global object: + +1. Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes. + +2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly dispatching **mutations**. This makes every state change easily track-able, and enables tooling that helps us better understand our applications. + +### The Simplest Store + +> **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! The doc also assumes you are already familiar with the concepts discussed in [Building Large-Scale Apps with Vue.js](http://vuejs.org/guide/application.html). + +Creating a Vuex store is pretty straightforward - just provide an initial state object, and some mutations: + +``` js +import Vuex from 'vuex' + +const state = { + count: 0 +} + +const mutations = { + INCREMENT (state) { + state.count++ + } +} + +export default new Vuex.Store({ + state, + mutations +}) +``` + +Now, you can access the state object as `store.state`, and trigger a mutation by dispatching its name: + +``` js +store.dispatch('INCREMENT') + +console.log(store.state.count) // -> 1 +``` + +If you prefer object-style dispatching, you can also do this: + +``` js +// same effect as above +store.dispatch({ + type: 'INCREMENT' +}) +``` + +Again, the reason we are dispatching a mutation instead of changing `store.state.count` directly, is because we want to explicitly track it. This simple convention makes your intention more explicit, so that you can reason about state changes in your app better when reading the code. In addition, this gives us the opportunity to implement tools that can log every mutation, take state snapshots, or even perform time travel debugging. + +Now this is just the simplest possible example of what a store is. But Vuex is more than just the store. Next, we will discuss some core concepts in depth: [State](state.md), [Mutations](mutations.md) and [Actions](actions.md). \ No newline at end of file From 917f4ce8330177b4377bba9bda36b3d75adb5591 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 21:19:50 +0900 Subject: [PATCH 06/80] Copy docs/en/getting-started.md from 2.0, understanding diff 1.0 and 2.0 --- docs/ja/getting-started.md | 47 +++++++++++++++----------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 788bf6f35..4f937a465 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -4,50 +4,41 @@ At the center of every Vuex application is the **store**. A "store" is basically 1. Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes. -2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly dispatching **mutations**. This makes every state change easily track-able, and enables tooling that helps us better understand our applications. +2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly **committing mutations**. This ensures every state change leaves a track-able record, and enables tooling that helps us better understand our applications. ### The Simplest Store -> **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! The doc also assumes you are already familiar with the concepts discussed in [Building Large-Scale Apps with Vue.js](http://vuejs.org/guide/application.html). +> **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! -Creating a Vuex store is pretty straightforward - just provide an initial state object, and some mutations: +After [installing](installation.md) Vuex, let's create a store. It is pretty straightforward - just provide an initial state object, and some mutations: ``` js -import Vuex from 'vuex' - -const state = { - count: 0 -} - -const mutations = { - INCREMENT (state) { - state.count++ +// Make sure to call Vue.use(Vuex) first if using a module system + +const store = new Vuex.Store({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } } -} - -export default new Vuex.Store({ - state, - mutations }) ``` -Now, you can access the state object as `store.state`, and trigger a mutation by dispatching its name: +Now, you can access the state object as `store.state`, and trigger a state change with the `store.commit` method: ``` js -store.dispatch('INCREMENT') +store.commit('increment') console.log(store.state.count) // -> 1 ``` -If you prefer object-style dispatching, you can also do this: +Again, the reason we are committing a mutation instead of changing `store.state.count` directly, is because we want to explicitly track it. This simple convention makes your intention more explicit, so that you can reason about state changes in your app better when reading the code. In addition, this gives us the opportunity to implement tools that can log every mutation, take state snapshots, or even perform time travel debugging. -``` js -// same effect as above -store.dispatch({ - type: 'INCREMENT' -}) -``` +Using store state in a component simply involves returning the state within a computed property, because the store state is reactive. Triggering changes simply means committing mutations in component methods. -Again, the reason we are dispatching a mutation instead of changing `store.state.count` directly, is because we want to explicitly track it. This simple convention makes your intention more explicit, so that you can reason about state changes in your app better when reading the code. In addition, this gives us the opportunity to implement tools that can log every mutation, take state snapshots, or even perform time travel debugging. +Here's an example of the [most basic Vuex counter app](https://jsfiddle.net/yyx990803/n9jmu5v7/). -Now this is just the simplest possible example of what a store is. But Vuex is more than just the store. Next, we will discuss some core concepts in depth: [State](state.md), [Mutations](mutations.md) and [Actions](actions.md). \ No newline at end of file +Next, we will discuss each core concept in much finer details, starting with [State](state.md). From 0be72fa8458d6d9a68fc90f86ee2fc5065245983 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 21:23:23 +0900 Subject: [PATCH 07/80] Copy docs/ja/getting-started.md from 1.0 --- docs/ja/getting-started.md | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 4f937a465..10fbbde8f 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -42,3 +42,57 @@ Using store state in a component simply involves returning the state within a co Here's an example of the [most basic Vuex counter app](https://jsfiddle.net/yyx990803/n9jmu5v7/). Next, we will discuss each core concept in much finer details, starting with [State](state.md). + +# Vuex 入門 + +Vuex アプリケーションの中心にあるものは **ストア** です。"ストア"は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが2つあります。 + +1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もし、ストアの状態が変化したら、ストアは、リアクティブかつ効率的に更新を行います。 + +2. ストアの状態を直接変更することはできません。明示的な **ミューテーション**のディスパッチによってのみ、ストアの状態を変更します。これによって、全ての状態の変更の追跡を容易にし、ツールでのアプリケーションの動作の理解を助けます。 + +### シンプルなストア + +> **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし、触れたことがなければ、[ぜひ、触れてください](https://babeljs.io/docs/learn-es2015/)! このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 + +Vuex ストアの作成は、かなり単純です。ストアオブジェクトの初期状態と、いくつかのミューテーションを準備するだけです。 + +``` js +import Vuex from 'vuex' + +const state = { + count: 0 +} + +const mutations = { + INCREMENT (state) { + state.count++ + } +} + +export default new Vuex.Store({ + state, + mutations +}) +``` + +`store.state` でストアオブジェクトの状態を参照でき、また、ミューションの名前でディスパッチすることで、ミューテーションをトリガーできます。 + +``` js +store.dispatch('INCREMENT') + +console.log(store.state.count) // -> 1 +``` + +もし、オブジェクトスタイルのディスパッチがよければ、以下のように記述して、おこなうことができます。 + +``` js +// 先の例と同じ効果 +store.dispatch({ + type: 'INCREMENT' +}) +``` + +もう一度、`store.state.count` を直接変更する代わりにミューテーションをディスパッチする理由について、確認しておきましょう。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。 + +今回は最も単純な例を通して、ストアが何かについて説明してきました。しかし、 Vuex はストアだけではありません。次から、[ステート](state.md)、[ミューテーション](mutations.md)、[アクション](actions.md)といった Vuex のコアコンセプトについて詳しく説明していきます。 From f37174429b734a9f27b4fe2389d1c1f094c5c0d7 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 21:26:35 +0900 Subject: [PATCH 08/80] Translated the part of getting-started.md which has no diff 1.0 and 2.0 --- docs/ja/getting-started.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 10fbbde8f..9f8fd91a1 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -1,12 +1,12 @@ -# Getting Started +# Vuex 入門 -At the center of every Vuex application is the **store**. A "store" is basically a container that holds your application **state**. There are two things that makes a Vuex store different from a plain global object: +Vuex アプリケーションの中心にあるものは **ストア** です。"ストア" は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。 -1. Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes. +1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もし、ストアの状態が変化したら、ストアは、リアクティブかつ効率的に更新を行います。 2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly **committing mutations**. This ensures every state change leaves a track-able record, and enables tooling that helps us better understand our applications. -### The Simplest Store +### シンプルなストア > **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! @@ -43,16 +43,8 @@ Here's an example of the [most basic Vuex counter app](https://jsfiddle.net/yyx9 Next, we will discuss each core concept in much finer details, starting with [State](state.md). -# Vuex 入門 - -Vuex アプリケーションの中心にあるものは **ストア** です。"ストア"は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが2つあります。 - -1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もし、ストアの状態が変化したら、ストアは、リアクティブかつ効率的に更新を行います。 - 2. ストアの状態を直接変更することはできません。明示的な **ミューテーション**のディスパッチによってのみ、ストアの状態を変更します。これによって、全ての状態の変更の追跡を容易にし、ツールでのアプリケーションの動作の理解を助けます。 -### シンプルなストア - > **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし、触れたことがなければ、[ぜひ、触れてください](https://babeljs.io/docs/learn-es2015/)! このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 Vuex ストアの作成は、かなり単純です。ストアオブジェクトの初期状態と、いくつかのミューテーションを準備するだけです。 From 5322189fa71eff9bfce853fafd771ab3a51d8548 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 22:39:57 +0900 Subject: [PATCH 09/80] Translate getting-started.md --- docs/ja/getting-started.md | 66 ++++++-------------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 9f8fd91a1..2fcac848f 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -1,19 +1,19 @@ # Vuex 入門 -Vuex アプリケーションの中心にあるものは **ストア** です。"ストア" は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。 +Vuex アプリケーションの中心にあるものは**ストア**です。"ストア" は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。 1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もし、ストアの状態が変化したら、ストアは、リアクティブかつ効率的に更新を行います。 -2. You cannot directly mutate the store's state. The only way to change a store's state is by explicitly **committing mutations**. This ensures every state change leaves a track-able record, and enables tooling that helps us better understand our applications. +2. ストアの状態を直接変更することはできません。明示的な**ミューテーション・コミット**によってのみ、ストアの状態を変更します。これによって、全ての状態の変更が追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。 ### シンプルなストア -> **NOTE:** We will be using ES2015 syntax for code examples for the rest of the docs. If you haven't picked it up, [you should](https://babeljs.io/docs/learn-es2015/)! +> **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし触れたことがなければ、[ぜひ触れてください](https://babeljs.io/docs/learn-es2015/)! このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 -After [installing](installation.md) Vuex, let's create a store. It is pretty straightforward - just provide an initial state object, and some mutations: +Vuex を[インストール](installation.md) してから、ストアをつくってみましょう。Vuex ストアの作成は、とても簡単です。ストアオブジェクトの初期状態と、いくつかのミューテーションを準備するだけです。 ``` js -// Make sure to call Vue.use(Vuex) first if using a module system +// module を使用しているときはあらかじめ Vue.use(Vuex) を呼び出していることを確認しておいてください const store = new Vuex.Store({ state: { @@ -27,7 +27,7 @@ const store = new Vuex.Store({ }) ``` -Now, you can access the state object as `store.state`, and trigger a state change with the `store.commit` method: +これで `store.state` でストアオブジェクトの状態を参照でき、また、`store.commit` メソッドで状態の変更を行うことができます。 ``` js store.commit('increment') @@ -35,56 +35,10 @@ store.commit('increment') console.log(store.state.count) // -> 1 ``` -Again, the reason we are committing a mutation instead of changing `store.state.count` directly, is because we want to explicitly track it. This simple convention makes your intention more explicit, so that you can reason about state changes in your app better when reading the code. In addition, this gives us the opportunity to implement tools that can log every mutation, take state snapshots, or even perform time travel debugging. +そして、`store.state.count` を直接変更する代わりにミューテーションをコミットする理由は、状態の変更を明確に追跡したいからです。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。 -Using store state in a component simply involves returning the state within a computed property, because the store state is reactive. Triggering changes simply means committing mutations in component methods. +ストアオブジェクトの状態はリアクティブなので、コンポーネント内のストアの状態は、演算されたプロパティ内の状態を返します。コンポーネントメソッドでミューテーションをコミットすることによって状態の変更を行います。 -Here's an example of the [most basic Vuex counter app](https://jsfiddle.net/yyx990803/n9jmu5v7/). +こちらが [Vuex を使った最も基本的なカウンターアプリの例](https://jsfiddle.net/yyx990803/n9jmu5v7/)です。 -Next, we will discuss each core concept in much finer details, starting with [State](state.md). - -2. ストアの状態を直接変更することはできません。明示的な **ミューテーション**のディスパッチによってのみ、ストアの状態を変更します。これによって、全ての状態の変更の追跡を容易にし、ツールでのアプリケーションの動作の理解を助けます。 - -> **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし、触れたことがなければ、[ぜひ、触れてください](https://babeljs.io/docs/learn-es2015/)! このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 - -Vuex ストアの作成は、かなり単純です。ストアオブジェクトの初期状態と、いくつかのミューテーションを準備するだけです。 - -``` js -import Vuex from 'vuex' - -const state = { - count: 0 -} - -const mutations = { - INCREMENT (state) { - state.count++ - } -} - -export default new Vuex.Store({ - state, - mutations -}) -``` - -`store.state` でストアオブジェクトの状態を参照でき、また、ミューションの名前でディスパッチすることで、ミューテーションをトリガーできます。 - -``` js -store.dispatch('INCREMENT') - -console.log(store.state.count) // -> 1 -``` - -もし、オブジェクトスタイルのディスパッチがよければ、以下のように記述して、おこなうことができます。 - -``` js -// 先の例と同じ効果 -store.dispatch({ - type: 'INCREMENT' -}) -``` - -もう一度、`store.state.count` を直接変更する代わりにミューテーションをディスパッチする理由について、確認しておきましょう。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。 - -今回は最も単純な例を通して、ストアが何かについて説明してきました。しかし、 Vuex はストアだけではありません。次から、[ステート](state.md)、[ミューテーション](mutations.md)、[アクション](actions.md)といった Vuex のコアコンセプトについて詳しく説明していきます。 +これから Vuex のコアコンセプトについて詳しく説明していきます。まずは[ステート](state.md)からはじめましょう。 From 98d29020dc437fa12f561d8b7c0a7c2a884e8cab Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 22:46:39 +0900 Subject: [PATCH 10/80] Make getting-started.md more readable --- docs/ja/getting-started.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 2fcac848f..869e7fffe 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -2,9 +2,9 @@ Vuex アプリケーションの中心にあるものは**ストア**です。"ストア" は、基本的にアプリケーションの**状態**を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。 -1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もし、ストアの状態が変化したら、ストアは、リアクティブかつ効率的に更新を行います。 +1. Vuex ストアはリアクティブです。 Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。 -2. ストアの状態を直接変更することはできません。明示的な**ミューテーション・コミット**によってのみ、ストアの状態を変更します。これによって、全ての状態の変更が追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。 +2. ストアの状態を直接変更することはできません。明示的に**ミューテーションをコミットする**ことによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。 ### シンプルなストア @@ -27,7 +27,7 @@ const store = new Vuex.Store({ }) ``` -これで `store.state` でストアオブジェクトの状態を参照でき、また、`store.commit` メソッドで状態の変更を行うことができます。 +これで `store.state` でストアオブジェクトの状態を参照でき、また `store.commit` メソッドで状態の変更を行うことができます。 ``` js store.commit('increment') @@ -35,7 +35,7 @@ store.commit('increment') console.log(store.state.count) // -> 1 ``` -そして、`store.state.count` を直接変更する代わりにミューテーションをコミットする理由は、状態の変更を明確に追跡したいからです。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。 +そして `store.state.count` を直接変更する代わりにミューテーションをコミットする理由は、状態の変更を明確に追跡したいからです。このシンプルな規約は、あなたのコードの意図をさらに明確にし、コードを読んだ時にアプリケーションの状態の変更について、論理的に考えることができるようにします。加えて、私たちに全ての変更のログを取ったり、状態のスナップショットを取ったり、タイムトラベルデバッグを行うようなツールを実装する余地を与えてくれます。 ストアオブジェクトの状態はリアクティブなので、コンポーネント内のストアの状態は、演算されたプロパティ内の状態を返します。コンポーネントメソッドでミューテーションをコミットすることによって状態の変更を行います。 From d93f15bc4a6b824cff1acfa3de85246790fc4584 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Fri, 11 Nov 2016 22:49:24 +0900 Subject: [PATCH 11/80] Tiny change --- docs/ja/getting-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ja/getting-started.md b/docs/ja/getting-started.md index 869e7fffe..0cfb1a90b 100644 --- a/docs/ja/getting-started.md +++ b/docs/ja/getting-started.md @@ -8,12 +8,12 @@ Vuex アプリケーションの中心にあるものは**ストア**です。" ### シンプルなストア -> **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし触れたことがなければ、[ぜひ触れてください](https://babeljs.io/docs/learn-es2015/)! このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 +> **注意:** 私たちは、このドキュメントのコード例に ES2015 のシンタックスを利用しています。 もし触れたことがなければ、[ぜひ触れてください](https://babeljs.io/docs/learn-es2015/)!このドキュメントは、他に[大規模アプリケーションの構築](https://jp.vuejs.org/guide/application.html)に書かれたコンセプトを既に読まれていることを前提にしています。 Vuex を[インストール](installation.md) してから、ストアをつくってみましょう。Vuex ストアの作成は、とても簡単です。ストアオブジェクトの初期状態と、いくつかのミューテーションを準備するだけです。 ``` js -// module を使用しているときはあらかじめ Vue.use(Vuex) を呼び出していることを確認しておいてください +// module を利用しているときはあらかじめ Vue.use(Vuex) を呼び出していることを確認しておいてください const store = new Vuex.Store({ state: { From aa9d6dc3f60add7a06ac3434de74c07353be57e6 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Sun, 20 Nov 2016 21:58:18 +0900 Subject: [PATCH 12/80] Translate intro --- docs/ja/intro.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/ja/intro.md b/docs/ja/intro.md index 8525800f1..79442c2d8 100644 --- a/docs/ja/intro.md +++ b/docs/ja/intro.md @@ -1,13 +1,23 @@ -# Vuex とは何か +しかし、単純さは、**共通の状態を共有する複数のコンポーネントを持ったときに**、あっさりと破綻します: -Vuex は Vue.js アプリケーションで状態を一元的に管理するためのアプリケーションアーキテクチャです。[Flux](https://facebook.github.io/flux/) や [Redux](https://github.com/rackt/redux) にインスパイアされていますが、それよりも単純化された概念を持ち、 Vue.js のリアクティブシステムの長所を生かすために、特別に設計された実装になっています。 +- 複数のビュー同士で同じ状態に依存することがあります。 +- 異なるビューからのアクションは、同じ状態を変更する必要があります。 -## なぜこれを必要とするのですか? +問題の一つ目は、props を渡すことは、深く入れ子になったコンポーネントに対しておこなうのは面倒で、単純に兄弟コンポーネントでは単純に動きません。問題の二つ目は、直接親子のインスタンスを参照したり、イベントを介して複数の状態のコピーを変更したり、同期することを試みるソリューションに頼っていることがよくあります。これらのパターンは、いずれも脆く、すぐにメンテナンスが困難なコードに繋がります。 -あなたのアプリケーションが十分に単純であるなら、おそらく Vuex は必要ないでしょう。早まって Vuex を適用しないようにしてください。しかし、中〜大規模な SPA を構築することになったら、Vue コンポーネントの外側の構造をより良くする方法を考える適切なタイミングです。そこで Vuex の出番です。 +では、コンポーネントから共有している状態を抽出し、それをグローバルシングルトンで管理するのはどうでしょうか? これにより、コンポーネントツリーは大きな "ビュー" となり、どのコンポーネントもツリー内のどこにあっても状態にアクセスしたり、アクションをトリガーできます! -Vue.js を単体で使用するとき、私達はコンポーネントの"内部"に状態を格納しようとする傾向があります。つまり、各コンポーネントは、アプリケーションの状態の一部を所有しており、結果として状態が様々な場所に散らばっています。しかし、時には一部の状態を、複数のコンポーネントの間で共有したいこともあるでしょう。このようなケースで、カスタムイベントシステムを利用して、あるコンポーネントがいくつかの状態を他のコンポーネントに"送信"する例がよく見られます。このパターンは、大規模なコンポーネントツリーでは、内部のイベントフローが瞬く間に複雑になり、正しく動作しない場合に原因を調査するのが困難になるという問題があります。 +さらに、状態管理に関わる概念を定義、および分離し、あるルールを敷くことで、コードの構造と保守性を向上させます。 -大規模アプリケーションで状態の共有をより良く対処するために、**コンポーネント内部の状態**と**アプリケーションレベルの状態**を区別する必要があります。アプリケーションレベルの状態は特定のコンポーネントに属していませんが、私達のコンポーネントはリアクティブな DOM 更新のためにまだそれを監視することができます。ひとつの場所で一元的に状態を管理することによって、複数のコンポーネントに影響を与える状態すべてがそこに属している必要があり、もはやイベントをあちこちに渡す必要はありません。加えて、全ての状態の変更の記録と検査がおこなわれることで、状態が変化する過程の理解を容易にし、さらにタイムトラベルデバッグのような興味深い機能も実装できます。 +Vuex の背景にある基本的なアイディアは、[Flux](https://facebook.github.io/flux/docs/overview.html)、 [Redux](http://redux.js.org/) そして [The Elm Architecture](https://guide.elm-lang.org/architecture/)から影響を受けたものです。 +他のパターンと異なり、Vuexは効率的な更新のために、Vue.jsの粒度の細かいリアクティビティシステムを利用するよう特別に調整された実装のライブラリです。 -Vuex は状態を管理するロジックの分割について、他にも様々な場所で一定の制約を強いてきますが、それでも実際のコード構造に対して十分な柔軟性を持っています。 +![vuex](./images/vuex.png) + +### いつ、Vuexを使うべきでしょうか? + +Vuex は、共有状態の管理に役立ちますが、さらに概念やボイラープレートのコストがかかります。これは、短期的生産性と長期的生産性のトレードオフです。 + +もし、あなたが大規模なSPAを構築することなく、Vuex を導入した場合、冗長で恐ろしいかんじになるかもしれません。そう感じることは全く普通です。もし、あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。シンプルな [グローバル イベント パス](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication) が必要なだけかもしれません。しかし、中規模から大規模のSPAを構築する場合は、Vue コンポーネント以外のどうやってうまく扱うか考える絶好の機会です。Vuex は自然な次のステップとなるでしょう。これは Redux の作者、Dan Abramov からの良い引用です: + +> Flux ライブラリは眼鏡のようなものです: それらが必要になったときに知ることになります。 \ No newline at end of file From ed57506a673f3a566073b3c607db7ffcf2b7c685 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Sat, 29 Oct 2016 14:30:46 +0900 Subject: [PATCH 13/80] translate intro --- docs/ja/images/flow.png | Bin 0 -> 69033 bytes docs/ja/images/vuex.png | Bin 0 -> 21959 bytes docs/ja/intro.md | 45 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 docs/ja/images/flow.png create mode 100644 docs/ja/images/vuex.png diff --git a/docs/ja/images/flow.png b/docs/ja/images/flow.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9b97d5947829d9a4ba6d1869b6f4c91b48f767 GIT binary patch literal 69033 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 EUl}O2b^rhX literal 0 HcmV?d00001 diff --git a/docs/ja/images/vuex.png b/docs/ja/images/vuex.png new file mode 100644 index 0000000000000000000000000000000000000000..0181299473acaed53c88ae6f38deb086eea824b7 GIT binary patch literal 21959 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 count }} + `, + // actions + methods: { + increment () { + this.count++ + } + } +}) +``` + +これはいくつかの要素をアプリ自身に含んでいます: + +- **状態**、これは私達のアプリを動かす信頼できる情報源(the source of truth)です。 +- **ビュー**、これは**状態**のただの宣言的なマッピングです。 +- **アクション**、これは**ビュー**からのユーザー入力に反応して、状態の変更を可能にする方法です。 + +これらは"片方向データフロー"のコンセプトの極めてシンプルな責務です: + +

+ +

+ +しかし、単純さは、**共通の状態を共有する複数のコンポーネントを持ったときに**、すぐに破綻します: - 複数のビュー同士で同じ状態に依存することがあります。 - 異なるビューからのアクションは、同じ状態を変更する必要があります。 @@ -20,4 +61,4 @@ Vuex は、共有状態の管理に役立ちますが、さらに概念やボイ もし、あなたが大規模なSPAを構築することなく、Vuex を導入した場合、冗長で恐ろしいかんじになるかもしれません。そう感じることは全く普通です。もし、あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。シンプルな [グローバル イベント パス](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication) が必要なだけかもしれません。しかし、中規模から大規模のSPAを構築する場合は、Vue コンポーネント以外のどうやってうまく扱うか考える絶好の機会です。Vuex は自然な次のステップとなるでしょう。これは Redux の作者、Dan Abramov からの良い引用です: -> Flux ライブラリは眼鏡のようなものです: それらが必要になったときに知ることになります。 \ No newline at end of file +> Flux ライブラリは眼鏡のようなものです: それらが必要になったときに知ることになります。 From ab7a095f9e936f57d54a0069292043ad7bd49cbf Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Tue, 22 Nov 2016 20:09:28 +0900 Subject: [PATCH 14/80] Copy docs/en/getters.md to ja --- docs/ja/getters.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/ja/getters.md diff --git a/docs/ja/getters.md b/docs/ja/getters.md new file mode 100644 index 000000000..6530ee08b --- /dev/null +++ b/docs/ja/getters.md @@ -0,0 +1,92 @@ + +# Getters + +Sometimes we may need to compute derived state based on store state, for example filtering through a list of items and counting them: + +``` js +computed: { + doneTodosCount () { + return this.$store.state.todos.filter(todo => todo.done).length + } +} +``` + +If more than one component needs to make use of this, we have to either duplicate the function, or extract it into a shared helper and import it in multiple places - both are less than ideal. + +Vuex allows us to define "getters" in the store (think of them as computed properties for stores). Getters will receive the state as their 1st argument: + +``` js +const store = new Vuex.Store({ + state: { + todos: [ + { id: 1, text: '...', done: true }, + { id: 2, text: '...', done: false } + ] + }, + getters: { + doneTodos: state => { + return state.todos.filter(todo => todo.done) + } + } +}) +``` + +The getters will be exposed on the `store.getters` object: + +``` js +store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] +``` + +Getters will also receive other getters as the 2nd argument: + +``` js +getters: { + // ... + doneTodosCount: (state, getters) => { + return getters.doneTodos.length + } +} +``` + +``` js +store.getters.doneTodosCount // -> 1 +``` + +We can now easily make use of it inside any component: + +``` js +computed: { + doneTodosCount () { + return this.$store.getters.doneTodosCount + } +} +``` + +### The `mapGetters` Helper + +The `mapGetters` helper simply maps store getters to local computed properties: + +``` js +import { mapGetters } from 'vuex' + +export default { + // ... + computed: { + // mix the getters into computed with object spread operator + ...mapGetters([ + 'doneTodosCount', + 'anotherGetter', + // ... + ]) + } +} +``` + +If you want to map a getter to a different name, use an object: + +``` js +mapGetters({ + // map this.doneCount to store.getters.doneTodosCount + doneCount: 'doneTodosCount' +}) +``` From 373e5ad5bc1f1cfe5600143f6b2880bab6057a88 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Tue, 22 Nov 2016 20:16:17 +0900 Subject: [PATCH 15/80] Translate getters.md --- docs/ja/getters.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ja/getters.md b/docs/ja/getters.md index 6530ee08b..909bcf67d 100644 --- a/docs/ja/getters.md +++ b/docs/ja/getters.md @@ -1,7 +1,7 @@ -# Getters +# ゲッター -Sometimes we may need to compute derived state based on store state, for example filtering through a list of items and counting them: +例えば項目のリストをフィルタリングしたりカウントするときのように、ストアの状態を算出したいときがあります。 ``` js computed: { @@ -11,9 +11,9 @@ computed: { } ``` -If more than one component needs to make use of this, we have to either duplicate the function, or extract it into a shared helper and import it in multiple places - both are less than ideal. +もしこの関数を複数のコンポーネントで利用したくなったら、関数をコピーするか、あるいは関数を共用のヘルパーに切り出して複数の場所でインポートする必要があります。- しかし、どちらも理想的とはいえません。 -Vuex allows us to define "getters" in the store (think of them as computed properties for stores). Getters will receive the state as their 1st argument: +Vuex を利用するとストア内に "ゲッター" を定義することができます(ストアのための算出プロパティだと考えてください)。ゲッターはストアを第1引数として受け取ります: ``` js const store = new Vuex.Store({ @@ -31,13 +31,13 @@ const store = new Vuex.Store({ }) ``` -The getters will be exposed on the `store.getters` object: +ゲッターは `store.getters` オブジェクトから取り出されます: ``` js store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] ``` -Getters will also receive other getters as the 2nd argument: +ゲッターは第2引数として他のゲッターを受け取ります: ``` js getters: { @@ -52,7 +52,7 @@ getters: { store.getters.doneTodosCount // -> 1 ``` -We can now easily make use of it inside any component: +どのコンポーネントの内部でも簡単にゲッターを利用することができます: ``` js computed: { From f756709255b4b5cfefab73423e981e5dfdff1dfe Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Tue, 22 Nov 2016 20:36:01 +0900 Subject: [PATCH 16/80] Translate mapGetters helper --- docs/ja/getters.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ja/getters.md b/docs/ja/getters.md index 909bcf67d..f3cf5f12f 100644 --- a/docs/ja/getters.md +++ b/docs/ja/getters.md @@ -62,10 +62,12 @@ computed: { } ``` -### The `mapGetters` Helper +### `mapGetters` ヘルパー The `mapGetters` helper simply maps store getters to local computed properties: +`mapGetters` ヘルパーはストアのゲッターをローカルの算出プロパティにマッピングさせます: + ``` js import { mapGetters } from 'vuex' @@ -82,7 +84,7 @@ export default { } ``` -If you want to map a getter to a different name, use an object: +ゲッターを異なる名前でマッピングさせたいときはオブジェクトを使います: ``` js mapGetters({ From ccdccb710e6419b252113ce6d7597e8c091f13c5 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 06:41:26 +0900 Subject: [PATCH 17/80] Copy docs/en/mutations.md from 1.0 --- docs/ja/mutations.md | 159 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/ja/mutations.md diff --git a/docs/ja/mutations.md b/docs/ja/mutations.md new file mode 100644 index 000000000..2c893c4d5 --- /dev/null +++ b/docs/ja/mutations.md @@ -0,0 +1,159 @@ +# Mutations + +Vuex mutations are essentially events: each mutation has a **name** and a **handler**. The handler function will receive the state as the first argument: + +``` js +import Vuex from 'vuex' + +const store = new Vuex.Store({ + state: { + count: 1 + }, + mutations: { + INCREMENT (state) { + // mutate state + state.count++ + } + } +}) +``` + +Using all caps for mutation names is just a convention to make it easier to differentiate them from plain functions. + +You cannot directly call a mutation handler. The options here is more like event registration: "When an `INCREMENT` event is dispatched, call this handler." To invoke a mutation handler, you need to dispatch a mutation event: + +``` js +store.dispatch('INCREMENT') +``` + +### Dispatch with Arguments + +It is also possible to pass along arguments: + +``` js +// ... +mutations: { + INCREMENT (state, n) { + state.count += n + } +} +``` +``` js +store.dispatch('INCREMENT', 10) +``` + +Here `10` will be passed to the mutation handler as the second argument following `state`. Same for any additional arguments. These arguments are called the **payload** for the given mutation. + +### Object-Style Dispatch + +You can also dispatch mutations using objects: + +``` js +store.dispatch({ + type: 'INCREMENT', + payload: 10 +}) +``` + +Note when using the object-style, you should include all arguments as properties on the dispatched object. The entire object will be passed as the second argument to mutation handlers: + +``` js +mutations: { + INCREMENT (state, mutation) { + state.count += mutation.payload + } +} +``` + +### Silent Dispatch + +In some scenarios you may not want the plugins to record the state change. Multiple dispatches to the store in a short period or polled do not always need to be tracked. In these situations it may be considered appropriate to silence the mutations. + +*Note:* This should be avoided where possible. Silent mutations break the contract of all state changes being tracked by the devtool. Use sparingly and where absolutely necessary. + +Dispatching without hitting plugins can be achieved with a `silent` flag. + +``` js +/** + * Example: Progress action. + * Dispatches often for changes that are not necessary to be tracked + **/ +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); +} +``` + +### Mutations Follow Vue's Reactivity Rules + +Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue: + +1. Prefer initializing your store's initial state with all desired fields upfront. + +2. When adding new properties to an Object, you should either: + + - Use `Vue.set(obj, 'newProp', 123)`, or - + + - Replace that Object with a fresh one. For example, using the stage-2 [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) we can write it like this: + + ``` js + state.obj = { ...state.obj, newProp: 123 } + ``` + +### Using Constants for Mutation Names + +It is also common to use constants for mutation names - they allow the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application: + +``` js +// mutation-types.js +export const SOME_MUTATION = 'SOME_MUTATION' +``` + +``` js +// store.js +import Vuex from 'vuex' +import { SOME_MUTATION } from './mutation-types' + +const store = new Vuex.Store({ + state: { ... }, + mutations: { + // we can use the ES2015 computed property name feature + // to use a constant as the function name + [SOME_MUTATION] (state) { + // mutate state + } + } +}) +``` + +Whether to use constants is largely a preference - it can be helpful in large projects with many developers, but it's totally optional if you don't like them. + +### Mutations Must Be Synchronous + +One important rule to remember is that **mutation handler functions must be synchronous**. Why? Consider the following example: + +``` js +mutations: { + SOME_MUTATION (state) { + api.callAsyncMethod(() => { + state.count++ + }) + } +} +``` + +Now imagine we are debugging the app and looking at our mutation logs. For every mutation logged, we want to be able to compare snapshots of the state *before* and *after* the mutation. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is dispatched, and we do not know when the callback will actually be called. Any state mutation performed in the callback is essentially un-trackable! + +### On to Actions + +Asynchronicity combined with state mutation can make your program very hard to reason about. For example, when you call two methods both with async callbacks that mutate the state, how do you know when they are called and which callback was called first? This is exactly why we want to separate the two concepts. In Vuex, we perform all state mutations in a synchronous manner. We will perform all asynchronous operations inside [Actions](actions.md). From 5f910777a301c8f41d4903c86a5f1e121b57f12a Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 06:42:21 +0900 Subject: [PATCH 18/80] Copy docs/en/mutations.md from 2.0, understanding diff 1.0 and 2.0 --- docs/ja/mutations.md | 133 ++++++++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 53 deletions(-) diff --git a/docs/ja/mutations.md b/docs/ja/mutations.md index 2c893c4d5..b782445a1 100644 --- a/docs/ja/mutations.md +++ b/docs/ja/mutations.md @@ -1,16 +1,14 @@ # Mutations -Vuex mutations are essentially events: each mutation has a **name** and a **handler**. The handler function will receive the state as the first argument: +The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string **type** and a **handler**. The handler function is where we perform actual state modifications, and it will receive the state as the first argument: ``` js -import Vuex from 'vuex' - const store = new Vuex.Store({ state: { count: 1 }, mutations: { - INCREMENT (state) { + increment (state) { // mutate state state.count++ } @@ -18,80 +16,81 @@ const store = new Vuex.Store({ }) ``` -Using all caps for mutation names is just a convention to make it easier to differentiate them from plain functions. - -You cannot directly call a mutation handler. The options here is more like event registration: "When an `INCREMENT` event is dispatched, call this handler." To invoke a mutation handler, you need to dispatch a mutation event: +You cannot directly call a mutation handler. The options here is more like event registration: "When a mutation with type `increment` is triggered, call this handler." To invoke a mutation handler, you need to call **store.commit** with its type: ``` js -store.dispatch('INCREMENT') +store.commit('increment') ``` -### Dispatch with Arguments +### Commit with Payload -It is also possible to pass along arguments: +You can pass an additional argument to `store.commit`, which is called the **payload** for the mutation: ``` js // ... mutations: { - INCREMENT (state, n) { + increment (state, n) { state.count += n } } ``` ``` js -store.dispatch('INCREMENT', 10) +store.commit('increment', 10) ``` -Here `10` will be passed to the mutation handler as the second argument following `state`. Same for any additional arguments. These arguments are called the **payload** for the given mutation. +In most cases, the payload should be an object so that it can contain multiple fields, and the recorded mutation will also be more descriptive: + +``` js +// ... +mutations: { + increment (state, payload) { + state.count += payload.amount + } +} +``` +``` js +store.commit('increment', { + amount: 10 +}) +``` -### Object-Style Dispatch +### Object-Style Commit -You can also dispatch mutations using objects: +An alternative way to commit a mutation is by directly using an object that has a `type` property: ``` js -store.dispatch({ - type: 'INCREMENT', - payload: 10 +store.commit({ + type: 'increment', + amount: 10 }) ``` -Note when using the object-style, you should include all arguments as properties on the dispatched object. The entire object will be passed as the second argument to mutation handlers: +When using object-style commit, the entire object will be passed as the payload to mutation handlers, so the handler remains the same: ``` js mutations: { - INCREMENT (state, mutation) { - state.count += mutation.payload + increment (state, payload) { + state.count += payload.amount } } ``` -### Silent Dispatch - -In some scenarios you may not want the plugins to record the state change. Multiple dispatches to the store in a short period or polled do not always need to be tracked. In these situations it may be considered appropriate to silence the mutations. +### Silent Commit -*Note:* This should be avoided where possible. Silent mutations break the contract of all state changes being tracked by the devtool. Use sparingly and where absolutely necessary. +> Note: This is a feature that will likely be deprecated once we implement mutation filtering in the devtools. -Dispatching without hitting plugins can be achieved with a `silent` flag. +By default, every committed mutation is sent to plugins (e.g. the devtools). However in some scenarios you may not want the plugins to record every state change. Multiple commits to the store in a short period or polled do not always need to be tracked. In such cases you can pass a third argument to `store.commit` to "silence" that specific mutation from plugins: ``` js -/** - * Example: Progress action. - * Dispatches often for changes that are not necessary to be tracked - **/ -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); -} +store.commit('increment', { + amount: 1 +}, { silent: true }) + +// with object-style commit +store.commit({ + type: 'increment', + amount: 1 +}, { silent: true }) ``` ### Mutations Follow Vue's Reactivity Rules @@ -104,15 +103,15 @@ Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vu - Use `Vue.set(obj, 'newProp', 123)`, or - - - Replace that Object with a fresh one. For example, using the stage-2 [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) we can write it like this: + - Replace that Object with a fresh one. For example, using the stage-3 [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) we can write it like this: - ``` js - state.obj = { ...state.obj, newProp: 123 } - ``` + ``` js + state.obj = { ...state.obj, newProp: 123 } + ``` -### Using Constants for Mutation Names +### Using Constants for Mutation Types -It is also common to use constants for mutation names - they allow the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application: +It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allow the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application: ``` js // mutation-types.js @@ -144,7 +143,7 @@ One important rule to remember is that **mutation handler functions must be sync ``` js mutations: { - SOME_MUTATION (state) { + someMutation (state) { api.callAsyncMethod(() => { state.count++ }) @@ -152,8 +151,36 @@ mutations: { } ``` -Now imagine we are debugging the app and looking at our mutation logs. For every mutation logged, we want to be able to compare snapshots of the state *before* and *after* the mutation. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is dispatched, and we do not know when the callback will actually be called. Any state mutation performed in the callback is essentially un-trackable! +Now imagine we are debugging the app and looking at the devtool's mutation logs. For every mutation logged, the devtool will need to capture a "before" and "after" snapshots of the state. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is committed, and there's no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable! + +### Commiting Mutations in Components + +You can commit mutations in components with `this.$store.commit('xxx')`, or use the `mapMutations` helper which maps component methods to `store.commit` calls (requires root `store` injection): + +``` js +import { mapMutations } from 'vuex' + +export default { + // ... + methods: { + ...mapMutations([ + 'increment' // map this.increment() to this.$store.commit('increment') + ]), + ...mapMutations({ + add: 'increment' // map this.add() to this.$store.commit('increment') + }) + } +} +``` ### On to Actions -Asynchronicity combined with state mutation can make your program very hard to reason about. For example, when you call two methods both with async callbacks that mutate the state, how do you know when they are called and which callback was called first? This is exactly why we want to separate the two concepts. In Vuex, we perform all state mutations in a synchronous manner. We will perform all asynchronous operations inside [Actions](actions.md). +Asynchronicity combined with state mutation can make your program very hard to reason about. For example, when you call two methods both with async callbacks that mutate the state, how do you know when they are called and which callback was called first? This is exactly why we want to separate the two concepts. In Vuex, **mutations are synchronous transactions**: + +``` js +store.commit('increment') +// any state change that the "increment" mutation may cause +// should be done at this moment. +``` + +To handle asynchronous operations, let's introduce [Actions](actions.md). From aff1ab507093954b2a2836ecaeda8e42edc2d761 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 06:44:24 +0900 Subject: [PATCH 19/80] Copy docs/ja/mutations.md from 1.0 --- docs/ja/mutations.md | 158 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/docs/ja/mutations.md b/docs/ja/mutations.md index b782445a1..6379ae6ea 100644 --- a/docs/ja/mutations.md +++ b/docs/ja/mutations.md @@ -184,3 +184,161 @@ store.commit('increment') ``` To handle asynchronous operations, let's introduce [Actions](actions.md). + +# ミューテーション + +Vuex のミューテーションは本質的にイベントです。各ミューテーションは**名前**と**ハンドラ**を持ちます。ハンドラ関数は常に Vuex の state を第1引数として取得します: + +``` js +import Vuex from 'vuex' + +const store = new Vuex.Store({ + state: { + count: 1 + }, + mutations: { + INCREMENT (state) { + // 状態の変更 + state.count++ + } + } +}) +``` + +ミューテーションの名前に全て大文字を使用するのは、容易に通常の関数と区別できるようにするための規約です。 + +直接ミューテーションハンドラを呼び出すことはできません。この mutations オプションは、どちらかいうと"`INCREMENT` イベントがディスパッチされるとき、このハンドラが呼ばれる"といったイベント登録のようなものです。ミューテーションハンドラを起動するためには、ミューテーションイベントをディスパッチする必要があります: + +``` js +store.dispatch('INCREMENT') +``` + +### 引数によるディスパッチ + +引数を渡すことも可能です: + +``` js +// ... +mutations: { + INCREMENT (state, n) { + state.count += n + } +} +``` +``` js +store.dispatch('INCREMENT', 10) +``` + +ここでの `10` は `state` に続く第2引数としてミューテーションハンドラに渡されます。さらに追加される引数についても同様です。これらの引数は、特定のミューテーションに対する**ペイロード**と呼びます。 + +### オブジェクトスタイルのディスパッチ + +またオブジェクトを利用してミューテーションをディスパッチすることもできます: + +```js +store.dispatch({ + type: 'INCREMENT', + payload: 10 +}) +``` + +オブジェクトスタイルを利用するとき、全ての引数をディスパッチされるオブジェクトのプロパティとして含めなければいけないことに注意してください。全体のオブジェクトは、ミューテーションハンドラの第2引数として渡されます。 + +``` js +mutations: { + INCREMENT (state, mutation) { + state.count += mutation.payload + } +} +``` + +### サイレントディスパッチ + +場合によっては、プラグインに状態の変化を記録して欲しくないこともあるでしょう。あるいは、短い間隔、ポーリングでのストアへの複数のディスパッチも、常に追跡する必要はないでしょう。これらの状況では、ミューテーションを沈黙( silence )させることが適切と考えることができます。 + +注意: サイレントディスパッチは可能な限り避けるべきです。サイレントミューテーションは、開発ツールの全ての状態の変更を追跡するという規約を壊します。絶対に必要だという状況で控えめに使用してください。 + +``` js +/** + * 例: プログレス アクション + * 追跡する必要がない変更を頻繁に送ります + **/ +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](https://github.com/sebmarkbage/ecmascript-rest-spread) を使用して、以下のように書くことができます: + + ``` js + state.obj = { ...state.obj, newProp: 123 } + ``` + +### ミューテーション名に定数を使用する + +ミューテーション名には定数を使用することが一般的です。これは、コードに対してリントツールのようなツールを利用できるという利点があり、また、単一ファイルに全ての定数を設定することで、共同で作業する人にアプリケーション全体で何のミューテーションが可能であるか一目見ただけで理解できるようにします: + +``` js +// mutation-types.js +export const SOME_MUTATION = 'SOME_MUTATION' +``` + +``` js +// 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) { + // 変異するステート + } + } +}) +``` + +定数を使用するかどうか大抵は好みであり、多くの開発者による大規模アプリケーションで役に立ちますが、もしお気に召さなければ、使用しなくても構いません。これは完全にオプションです。 + +### ミューテーションは同期的でなければならない + +ひとつの重要なルールを覚えておきましょう。それはミューテーションハンドラ関数は同期的でなければならないということです。なぜか? 次の例で考えてみましょう: + +```js +mutations: { + SOME_MUTATION (state) { + api.callAsyncMethod(() => { + state.count++ + }) + } +} +``` + +いま、ミューテーションのログを見て、アプリケーションのデバッグを行っていることを想像してください。全てのミューテーションはログに記録されていて、ミューテーションの前後の状態のスナップショットを比較することが可能です。しかし、例のミューテーション内の非同期コールバックは、それを不可能にします: そのコールバックは、ミューテーションがディスパッチされたときにまだ呼ばれません。そして、コールバックが実際いつ呼ばれるかは分かりません。いかなる状態変更でも、コールバック内で起きる場合は本質的に追跡不可能です。 + +### アクションに続けて + +状態変更を非同期に組み合わせることは、プログラムの動きを予測することを非常に困難にするかもしれません。例えば、状態を変更する非同期コールバックを持った2つのメソッドを両方呼び出しとき、どうやってそれらが呼び出されたか、あるいは先に呼び出されたかのはどちらかなのか知ればよいのでしょう? 状態変更と非同期の2つの概念を分離したいという理由は、はっきりしています。 Vuex では、全ての状態変更は同期的におこなうという作法になっています。全ての非同期命令は [アクション](actions.md) の内部でおこなうことになるでしょう。 From 52f02b67bb9470907f64d469e8a45a7a444dd00a Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 06:55:20 +0900 Subject: [PATCH 20/80] Translate mutations.md --- docs/ja/mutations.md | 230 +++++++------------------------------------ 1 file changed, 36 insertions(+), 194 deletions(-) diff --git a/docs/ja/mutations.md b/docs/ja/mutations.md index 6379ae6ea..b74023993 100644 --- a/docs/ja/mutations.md +++ b/docs/ja/mutations.md @@ -1,6 +1,6 @@ # Mutations -The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string **type** and a **handler**. The handler function is where we perform actual state modifications, and it will receive the state as the first argument: +実際に Vuex のストアの状態を変更できる唯一の方法は、ミューテーションをコミットすることです。Vuex のミューテーションはイベントにとても近い概念です: 各ミューテーションは**タイプ**と**ハンドラ**を持ちます。ハンドラ関数は Vuex の state を第1引数として取得し、実際に状態の変更を行います: ``` js const store = new Vuex.Store({ @@ -16,15 +16,15 @@ const store = new Vuex.Store({ }) ``` -You cannot directly call a mutation handler. The options here is more like event registration: "When a mutation with type `increment` is triggered, call this handler." To invoke a mutation handler, you need to call **store.commit** with its type: +直接ミューテーションハンドラを呼び出すことはできません。この mutations オプションは、どちらかいうと "タイプが `increment` のミューテーションがトリガーされたときに、このハンドラが呼ばれる" といったイベント登録のようなものです。ミューテーションハンドラを起動するためにはミューテーションのタイプを指定して **store.commit** を呼び出す必要があります: ``` js store.commit('increment') ``` -### Commit with Payload +### 追加の引数を渡してコミットする -You can pass an additional argument to `store.commit`, which is called the **payload** for the mutation: +`store.commit` に追加の引数を渡すこともできます。この追加の引数は、特定のミューテーションに対する**ペイロード**と呼びます: ``` js // ... @@ -34,11 +34,12 @@ mutations: { } } ``` + ``` js store.commit('increment', 10) ``` -In most cases, the payload should be an object so that it can contain multiple fields, and the recorded mutation will also be more descriptive: +ほとんどの場合、ペイロードはオブジェクトにすべきです。そうすることで複数のフィールドを含められるようになり、またミューテーションがより記述的に記録されるようになります: ``` js // ... @@ -48,15 +49,16 @@ mutations: { } } ``` + ``` js store.commit('increment', { amount: 10 }) ``` -### Object-Style Commit +### オブジェクトスタイルのコミット -An alternative way to commit a mutation is by directly using an object that has a `type` property: +また `type` プロパティを持つオブジェクトを使って、ミューテーションをコミットすることもできます: ``` js store.commit({ @@ -65,7 +67,7 @@ store.commit({ }) ``` -When using object-style commit, the entire object will be passed as the payload to mutation handlers, so the handler remains the same: +オブジェクトスタイルでコミットするとき、オブジェクト全体がペイロードとしてミューテーションハンドラに渡されます。したがってハンドラの中で同じものが利用できます: ``` js mutations: { @@ -75,43 +77,43 @@ mutations: { } ``` -### Silent Commit +### サイレントコミット -> Note: This is a feature that will likely be deprecated once we implement mutation filtering in the devtools. +> 注意: この機能は開発ツール内にミューテーション・フィルタが実装された後に非推奨になる予定です。 -By default, every committed mutation is sent to plugins (e.g. the devtools). However in some scenarios you may not want the plugins to record every state change. Multiple commits to the store in a short period or polled do not always need to be tracked. In such cases you can pass a third argument to `store.commit` to "silence" that specific mutation from plugins: +デフォルトでは全てのコミットされたミューテーションはプラグイン(開発ツール等)に送られます。しかし場合によっては、プラグインに全ての状態の変化を記録して欲しくないこともあるでしょう。あるいは、短い間隔やポーリングでのストアへの複数のコミットも、常に追跡する必要はないでしょう。こうしたケースでは、`store.commit` に第3引数を渡すことで、特定のミューテーションをプラグインに気付かせないようにすること("silence")ができます: ``` js store.commit('increment', { amount: 1 }, { silent: true }) -// with object-style commit +// オブジェクトスタイルのコミット store.commit({ type: 'increment', amount: 1 }, { silent: true }) ``` -### Mutations Follow Vue's Reactivity Rules +### Vue のリアクティブなルールに則ったミューテーション -Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue: +Vuex ストアのステートは Vue によってリアクティブになっているので、ステートを変更すると、ステートを監視している Vue コンポーネントは自動的に更新されます。これは Vuex のミューテーションは、通常の Vue で動作させているときと同じリアクティブな警告の対象となることを意味します: -1. Prefer initializing your store's initial state with all desired fields upfront. +1. あらかじめ全ての必要なフィールドによって、ストアの初期状態を初期化することが望ましいです -2. When adding new properties to an Object, you should either: +2. 新しいプロパティをオブジェクトに追加するとき、以下のいずれかが必要です: - - Use `Vue.set(obj, 'newProp', 123)`, or - + - `Vue.set(obj, 'newProp', 123)` を使用する。あるいは - - Replace that Object with a fresh one. For example, using the stage-3 [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) we can write it like this: + - 全く新しいオブジェクトで既存のオブジェクトを置き換える。例えば、stage-3 の [object spread syntax](https://github.com/sebmarkbage/ecmascript-rest-spread) を使用して、以下のように書くことができます: ``` js state.obj = { ...state.obj, newProp: 123 } ``` -### Using Constants for Mutation Types +### ミューテーション・タイプに定数を使用する -It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allow the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application: +いろいろな Flux 実装において、ミューテーション・タイプに定数を使用することが一般的だとされています。これはコードに対してリントツールのようなツールを利用できるという利点があり、また単一ファイルに全ての定数を設定することによって、共同で作業する人に、アプリケーション全体で何のミューテーションが可能であるかを一目見ただけで理解できるようにします: ``` js // mutation-types.js @@ -126,20 +128,19 @@ import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { - // we can use the ES2015 computed property name feature - // to use a constant as the function name + // 定数を関数名として使用できる ES2015 の算出プロパティ(computed property)名機能を使用できます [SOME_MUTATION] (state) { - // mutate state + // 状態を変更する } } }) ``` -Whether to use constants is largely a preference - it can be helpful in large projects with many developers, but it's totally optional if you don't like them. +定数を使用するかどうかは好みの問題です。多くの開発者による大規模なプロジェクトで役に立ちますが、完全にオプションなので、もしお気に召さなければ使用しなくても構いません。 -### Mutations Must Be Synchronous +### ミューテーションは同期的でなければならない -One important rule to remember is that **mutation handler functions must be synchronous**. Why? Consider the following example: +ひとつの重要なルールを覚えておきましょう。それは**ミューテーションハンドラ関数は同期的でなければならない**ということです。なぜか?次の例で考えてみましょう: ``` js mutations: { @@ -151,11 +152,11 @@ mutations: { } ``` -Now imagine we are debugging the app and looking at the devtool's mutation logs. For every mutation logged, the devtool will need to capture a "before" and "after" snapshots of the state. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is committed, and there's no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable! +いま、開発ツールのミューテーションのログを見ながら、アプリケーションのデバッグを行っていることを想像してください。全てのミューテーションをログに記録するためには、ミューテーションの前後の状態のスナップショットを捕捉することが必要です。しかし、上の例にあるミューテーション内の非同期コールバックは、それを不可能にします: そのコールバックは、ミューテーションがコミットされた時点ではまだ呼び出されていません。そして、コールバックが実際にいつ呼び出されるかを、開発ツールは知る術がありません。いかなる状態変更でも、コールバック内で起きる場合は本質的に追跡不可能です。 -### Commiting Mutations in Components +### コンポーネント内におけるミューテーションのコミット -You can commit mutations in components with `this.$store.commit('xxx')`, or use the `mapMutations` helper which maps component methods to `store.commit` calls (requires root `store` injection): +`this.$store.commit('xxx')` と書くか、もしくはコンポーネントのメソッドを `store.commit` にマッピングする `mapMutations` ヘルパーを呼び出すこと(ルートの `store` が必要)で、コンポーネント内でミューテーションをコミットできます: ``` js import { mapMutations } from 'vuex' @@ -164,181 +165,22 @@ export default { // ... methods: { ...mapMutations([ - 'increment' // map this.increment() to this.$store.commit('increment') + 'increment' // this.increment() を this.$store.commit('increment') にマッピングする ]), ...mapMutations({ - add: 'increment' // map this.add() to this.$store.commit('increment') + add: 'increment' // this.add() を this.$store.commit('increment') にマッピングする }) } } ``` -### On to Actions +### アクションへ向けて -Asynchronicity combined with state mutation can make your program very hard to reason about. For example, when you call two methods both with async callbacks that mutate the state, how do you know when they are called and which callback was called first? This is exactly why we want to separate the two concepts. In Vuex, **mutations are synchronous transactions**: +状態変更を非同期に組み合わせることは、プログラムの動きを予測することを非常に困難にします。例えば、状態を変更する非同期コールバックを持った 2つのメソッドを両方呼び出しとき、それらがいつ呼び出されたか、どちらが先に呼び出されたかを、どうやって知ればよいのでしょう?これがまさに、状態変更と非同期の 2つの概念を分離したいという理由です。Vuex では**全てのミューテーションは同期的に行う**という作法になっています: ``` js store.commit('increment') -// any state change that the "increment" mutation may cause -// should be done at this moment. -``` - -To handle asynchronous operations, let's introduce [Actions](actions.md). - -# ミューテーション - -Vuex のミューテーションは本質的にイベントです。各ミューテーションは**名前**と**ハンドラ**を持ちます。ハンドラ関数は常に Vuex の state を第1引数として取得します: - -``` js -import Vuex from 'vuex' - -const store = new Vuex.Store({ - state: { - count: 1 - }, - mutations: { - INCREMENT (state) { - // 状態の変更 - state.count++ - } - } -}) -``` - -ミューテーションの名前に全て大文字を使用するのは、容易に通常の関数と区別できるようにするための規約です。 - -直接ミューテーションハンドラを呼び出すことはできません。この mutations オプションは、どちらかいうと"`INCREMENT` イベントがディスパッチされるとき、このハンドラが呼ばれる"といったイベント登録のようなものです。ミューテーションハンドラを起動するためには、ミューテーションイベントをディスパッチする必要があります: - -``` js -store.dispatch('INCREMENT') -``` - -### 引数によるディスパッチ - -引数を渡すことも可能です: - -``` js -// ... -mutations: { - INCREMENT (state, n) { - state.count += n - } -} -``` -``` js -store.dispatch('INCREMENT', 10) -``` - -ここでの `10` は `state` に続く第2引数としてミューテーションハンドラに渡されます。さらに追加される引数についても同様です。これらの引数は、特定のミューテーションに対する**ペイロード**と呼びます。 - -### オブジェクトスタイルのディスパッチ - -またオブジェクトを利用してミューテーションをディスパッチすることもできます: - -```js -store.dispatch({ - type: 'INCREMENT', - payload: 10 -}) -``` - -オブジェクトスタイルを利用するとき、全ての引数をディスパッチされるオブジェクトのプロパティとして含めなければいけないことに注意してください。全体のオブジェクトは、ミューテーションハンドラの第2引数として渡されます。 - -``` js -mutations: { - INCREMENT (state, mutation) { - state.count += mutation.payload - } -} +// "increment" ミューテーションによる状態変更は、この時点で行われるすべき ``` -### サイレントディスパッチ - -場合によっては、プラグインに状態の変化を記録して欲しくないこともあるでしょう。あるいは、短い間隔、ポーリングでのストアへの複数のディスパッチも、常に追跡する必要はないでしょう。これらの状況では、ミューテーションを沈黙( silence )させることが適切と考えることができます。 - -注意: サイレントディスパッチは可能な限り避けるべきです。サイレントミューテーションは、開発ツールの全ての状態の変更を追跡するという規約を壊します。絶対に必要だという状況で控えめに使用してください。 - -``` js -/** - * 例: プログレス アクション - * 追跡する必要がない変更を頻繁に送ります - **/ -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](https://github.com/sebmarkbage/ecmascript-rest-spread) を使用して、以下のように書くことができます: - - ``` js - state.obj = { ...state.obj, newProp: 123 } - ``` - -### ミューテーション名に定数を使用する - -ミューテーション名には定数を使用することが一般的です。これは、コードに対してリントツールのようなツールを利用できるという利点があり、また、単一ファイルに全ての定数を設定することで、共同で作業する人にアプリケーション全体で何のミューテーションが可能であるか一目見ただけで理解できるようにします: - -``` js -// mutation-types.js -export const SOME_MUTATION = 'SOME_MUTATION' -``` - -``` js -// 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) { - // 変異するステート - } - } -}) -``` - -定数を使用するかどうか大抵は好みであり、多くの開発者による大規模アプリケーションで役に立ちますが、もしお気に召さなければ、使用しなくても構いません。これは完全にオプションです。 - -### ミューテーションは同期的でなければならない - -ひとつの重要なルールを覚えておきましょう。それはミューテーションハンドラ関数は同期的でなければならないということです。なぜか? 次の例で考えてみましょう: - -```js -mutations: { - SOME_MUTATION (state) { - api.callAsyncMethod(() => { - state.count++ - }) - } -} -``` - -いま、ミューテーションのログを見て、アプリケーションのデバッグを行っていることを想像してください。全てのミューテーションはログに記録されていて、ミューテーションの前後の状態のスナップショットを比較することが可能です。しかし、例のミューテーション内の非同期コールバックは、それを不可能にします: そのコールバックは、ミューテーションがディスパッチされたときにまだ呼ばれません。そして、コールバックが実際いつ呼ばれるかは分かりません。いかなる状態変更でも、コールバック内で起きる場合は本質的に追跡不可能です。 - -### アクションに続けて - -状態変更を非同期に組み合わせることは、プログラムの動きを予測することを非常に困難にするかもしれません。例えば、状態を変更する非同期コールバックを持った2つのメソッドを両方呼び出しとき、どうやってそれらが呼び出されたか、あるいは先に呼び出されたかのはどちらかなのか知ればよいのでしょう? 状態変更と非同期の2つの概念を分離したいという理由は、はっきりしています。 Vuex では、全ての状態変更は同期的におこなうという作法になっています。全ての非同期命令は [アクション](actions.md) の内部でおこなうことになるでしょう。 +非同期的な命令を扱うために [アクション](actions.md) を見てみましょう。 From bde44a0f68444c1a980a36df3b678a1b56e02e7c Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 09:02:37 +0900 Subject: [PATCH 21/80] Delete unnecessary rows --- docs/en/getters.md | 1 - docs/ja/getters.md | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/en/getters.md b/docs/en/getters.md index 6530ee08b..ffd831841 100644 --- a/docs/en/getters.md +++ b/docs/en/getters.md @@ -1,4 +1,3 @@ - # Getters Sometimes we may need to compute derived state based on store state, for example filtering through a list of items and counting them: diff --git a/docs/ja/getters.md b/docs/ja/getters.md index f3cf5f12f..4c28d9315 100644 --- a/docs/ja/getters.md +++ b/docs/ja/getters.md @@ -1,4 +1,3 @@ - # ゲッター 例えば項目のリストをフィルタリングしたりカウントするときのように、ストアの状態を算出したいときがあります。 From 938798304932e18620a104fac99820cff554af9f Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 10:24:17 +0900 Subject: [PATCH 22/80] Copy actions.md from 1.0 --- docs/ja/actions.md | 199 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 docs/ja/actions.md diff --git a/docs/ja/actions.md b/docs/ja/actions.md new file mode 100644 index 000000000..3bb36bfb3 --- /dev/null +++ b/docs/ja/actions.md @@ -0,0 +1,199 @@ +# Actions + +> Vuex actions are in fact "action creators" in vanilla flux definitions, but I find that term more confusing than useful. + +Actions are just functions that dispatch mutations. By convention, Vuex actions always expect a store instance as its first argument, followed by optional additional arguments: + +``` js +// the simplest action +function increment (store) { + store.dispatch('INCREMENT') +} + +// an action with additional arguments +// with ES2015 argument destructuring +function incrementBy ({ dispatch }, amount) { + dispatch('INCREMENT', amount) +} +``` + +This may look dumb at first sight: why don't we just dispatch mutations directly? Well, remember that **mutations must be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: + +``` js +function incrementAsync ({ dispatch }) { + setTimeout(() => { + dispatch('INCREMENT') + }, 1000) +} +``` + +A more practical example would be an action to checkout a shopping cart, which involves **calling an async API** and **dispatching multiple mutations**: + +``` js +function checkout ({ dispatch, state }, products) { + // save the current in cart items + const savedCartItems = [...state.cart.added] + // send out checkout request, and optimistically + // clear the cart + dispatch(types.CHECKOUT_REQUEST) + // the shop API accepts a success callback and a failure callback + shop.buyProducts( + products, + // handle success + () => dispatch(types.CHECKOUT_SUCCESS), + // handle failure + () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) + ) +} +``` + +Note that instead of expecting returns values or passing callbacks to actions, the result of calling the async API is handled by dispatching mutations as well. The rule of thumb is that **the only side effects produced by calling actions should be dispatched mutations**. + +### Calling Actions In Components + +You may have noticed that action functions are not directly callable without reference to a store instance. Technically, we can invoke an action by calling `action(this.$store)` inside a method, but it's better if we can directly expose "bound" versions of actions as the component's methods so that we can easily refer to them inside templates. We can do that using the `vuex.actions` option: + +``` js +// inside a component +import { incrementBy } from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, // state getters + actions: { + incrementBy // ES6 object literal shorthand, bind using the same name + } + } +}) +``` + +What the above code does is to bind the raw `incrementBy` action to the component's store instance, and expose it on the component as an instance method, `vm.incrementBy`. Any arguments passed to `vm.incrementBy` will be passed to the raw action function after the first argument which is the store, so calling: + +``` js +vm.incrementBy(1) +``` + +is equivalent to: + +``` js +incrementBy(vm.$store, 1) +``` + +But the benefit is that we can bind to it more easily inside the component's template: + +``` html + +``` + +You can obviously use a different method name when binding actions: + +``` js +// inside a component +import { incrementBy } from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, + actions: { + plus: incrementBy // bind using a different name + } + } +}) +``` + +Now the action will be bound as `vm.plus` instead of `vm.incrementBy`. + +### Inline Actions + +If an action is specific to a component, you can take the shortcut and just define it inline: + +``` js +const vm = new Vue({ + vuex: { + getters: { ... }, + actions: { + plus: ({ dispatch }) => dispatch('INCREMENT') + } + } +}) +``` + +### Binding All Actions + +If you simply want to bind all the shared actions: + +``` js +import * as actions from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, + actions // bind all actions + } +}) +``` + +### Arrange Actions in Modules + +Normally in large applications, actions should be arranged in groups/modules for different purposes. For example, userActions module deals with user registration, login, logout, and so on, while shoppingCartActions module deals with other tasks for shopping. + +Modularization is more convenient for different components to import only required actions. + +You may import action module into action module for reusability. + +```javascript +// errorActions.js +export const setError = ({dispatch}, error) => { + dispatch('SET_ERROR', error) +} +export const showError = ({dispatch}) => { + dispatch('SET_ERROR_VISIBLE', true) +} +export const hideError = ({dispatch}) => { + dispatch('SET_ERROR_VISIBLE', false) +} +``` + +```javascript +// userActions.js +import {setError, showError} from './errorActions' + +export const login = ({dispatch}, username, password) => { + if (username && password) { + doLogin(username, password).done(res => { + dispatch('SET_USERNAME', res.username) + dispatch('SET_LOGGED_IN', true) + dispatch('SET_USER_INFO', res) + }).fail(error => { + dispatch('SET_INVALID_LOGIN') + setError({dispatch}, error) + showError({dispatch}) + }) + } +} + +``` + +While calling actions from another module, or while calling another action in the same module, remember that actions take a store instance as its first argument, so the action called inside another action should be passed through the first argument for the caller. + +If you write the action with ES6 destructuring style, make sure that the first argument of the caller action covers all the properties and methods of both actions. For example, only *dispatch* is used in the caller action and *state*, *watch* are used in the called action, all the *dispatch*, *state* and *watch* should be presented in the caller first formal argument like this: + +```javascript +import {callee} from './anotherActionModule' + +export const caller = ({dispatch, state, watch}) => { + dispatch('MUTATION_1') + callee({state, watch}) +} +``` + +Otherwise, you should use the old-fashioned function syntax: + +```javascript +import {callee} from './anotherActionModule' + +export const caller = (store) => { + store.dispatch('MUTATION_1') + callee(store) +} +``` From 84e162bfe141bd4f2ede28643208894b9e752576 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 10:25:11 +0900 Subject: [PATCH 23/80] Copy actions.md from 2.0, understanding diff 1.0 and 2.0 --- docs/ja/actions.md | 258 ++++++++++++++++++++------------------------- 1 file changed, 117 insertions(+), 141 deletions(-) diff --git a/docs/ja/actions.md b/docs/ja/actions.md index 3bb36bfb3..b5e9a2ebe 100644 --- a/docs/ja/actions.md +++ b/docs/ja/actions.md @@ -1,199 +1,175 @@ # Actions -> Vuex actions are in fact "action creators" in vanilla flux definitions, but I find that term more confusing than useful. +Actions are similar to mutations, the difference being that: -Actions are just functions that dispatch mutations. By convention, Vuex actions always expect a store instance as its first argument, followed by optional additional arguments: +- Instead of mutating the state, actions commit mutations. +- Actions can contain arbitrary asynchronous operations. -``` js -// the simplest action -function increment (store) { - store.dispatch('INCREMENT') -} +Let's register a simple action: -// an action with additional arguments -// with ES2015 argument destructuring -function incrementBy ({ dispatch }, amount) { - dispatch('INCREMENT', amount) -} +``` js +const store = new Vuex.Store({ + state: { + count: 0 + }, + mutations: { + increment (state) { + state.count++ + } + }, + actions: { + increment (context) { + context.commit('increment') + } + } +}) ``` -This may look dumb at first sight: why don't we just dispatch mutations directly? Well, remember that **mutations must be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: +Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call `context.commit` to commit a mutation, or access the state and getters via `context.state` and `context.getters`. We will see why this context object is not the store instance itself when we introduce [Modules](modules.md) later. + +In practice, we often use ES2015 [argument destructuring](https://github.com/lukehoban/es6features#destructuring) to simplify the code a bit (especially when we need to call `commit` multiple times): ``` js -function incrementAsync ({ dispatch }) { - setTimeout(() => { - dispatch('INCREMENT') - }, 1000) +actions: { + increment ({ commit }) { + commit('increment') + } } ``` -A more practical example would be an action to checkout a shopping cart, which involves **calling an async API** and **dispatching multiple mutations**: +### Dispatching Actions + +Actions are triggered with the `store.dispatch` method: ``` js -function checkout ({ dispatch, state }, products) { - // save the current in cart items - const savedCartItems = [...state.cart.added] - // send out checkout request, and optimistically - // clear the cart - dispatch(types.CHECKOUT_REQUEST) - // the shop API accepts a success callback and a failure callback - shop.buyProducts( - products, - // handle success - () => dispatch(types.CHECKOUT_SUCCESS), - // handle failure - () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) - ) -} +store.dispatch('increment') ``` -Note that instead of expecting returns values or passing callbacks to actions, the result of calling the async API is handled by dispatching mutations as well. The rule of thumb is that **the only side effects produced by calling actions should be dispatched mutations**. - -### Calling Actions In Components - -You may have noticed that action functions are not directly callable without reference to a store instance. Technically, we can invoke an action by calling `action(this.$store)` inside a method, but it's better if we can directly expose "bound" versions of actions as the component's methods so that we can easily refer to them inside templates. We can do that using the `vuex.actions` option: +This may look dumb at first sight: if we want to increment the count, why don't we just call `store.commit('increment')` directly? Well, remember that **mutations must be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: ``` js -// inside a component -import { incrementBy } from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, // state getters - actions: { - incrementBy // ES6 object literal shorthand, bind using the same name - } +actions: { + incrementAsync ({ commit }) { + setTimeout(() => { + commit('increment') + }, 1000) } -}) +} ``` -What the above code does is to bind the raw `incrementBy` action to the component's store instance, and expose it on the component as an instance method, `vm.incrementBy`. Any arguments passed to `vm.incrementBy` will be passed to the raw action function after the first argument which is the store, so calling: +Actions support the same payload format and object-style dispatch: ``` js -vm.incrementBy(1) +// dispatch with a payload +store.dispatch('incrementAsync', { + amount: 10 +}) + +// dispatch with an object +store.dispatch({ + type: 'incrementAsync', + amount: 10 +}) ``` -is equivalent to: +A more practical example of real-world actions would be an action to checkout a shopping cart, which involves **calling an async API** and **committing multiple mutations**: ``` js -incrementBy(vm.$store, 1) +actions: { + checkout ({ commit, state }, payload) { + // save the items currently in the cart + const savedCartItems = [...state.cart.added] + // send out checkout request, and optimistically + // clear the cart + commit(types.CHECKOUT_REQUEST) + // the shop API accepts a success callback and a failure callback + shop.buyProducts( + products, + // handle success + () => commit(types.CHECKOUT_SUCCESS), + // handle failure + () => commit(types.CHECKOUT_FAILURE, savedCartItems) + ) + } +} ``` -But the benefit is that we can bind to it more easily inside the component's template: +Note we are performing a flow of asynchronous operations, and recording the side effects (state mutations) of the action by committing them. -``` html - -``` +### Dispatching Actions in Components -You can obviously use a different method name when binding actions: +You can dispatch actions in components with `this.$store.dispatch('xxx')`, or use the `mapActions` helper which maps component methods to `store.dispatch` calls (requires root `store` injection): ``` js -// inside a component -import { incrementBy } from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, - actions: { - plus: incrementBy // bind using a different name - } +import { mapActions } from 'vuex' + +export default { + // ... + methods: { + ...mapActions([ + 'increment' // map this.increment() to this.$store.dispatch('increment') + ]), + ...mapActions({ + add: 'increment' // map this.add() to this.$store.dispatch('increment') + }) } -}) +} ``` -Now the action will be bound as `vm.plus` instead of `vm.incrementBy`. +### Composing Actions -### Inline Actions +Actions are often asynchronous, so how do we know when an action is done? And more importantly, how can we compose multiple actions together to handle more complex async flows? -If an action is specific to a component, you can take the shortcut and just define it inline: +The first thing to know is that `store.dispatch` returns the value returned by the triggered action handler, so you can return a Promise in an action: ``` js -const vm = new Vue({ - vuex: { - getters: { ... }, - actions: { - plus: ({ dispatch }) => dispatch('INCREMENT') - } +actions: { + actionA ({ commit }) { + return new Promise((resolve, reject) => { + setTimeout(() => { + commit('someMutation') + resolve() + }, 1000) + }) } -}) +} ``` -### Binding All Actions - -If you simply want to bind all the shared actions: +Now you can do: ``` js -import * as actions from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, - actions // bind all actions - } +store.dispatch('actionA').then(() => { + // ... }) ``` -### Arrange Actions in Modules - -Normally in large applications, actions should be arranged in groups/modules for different purposes. For example, userActions module deals with user registration, login, logout, and so on, while shoppingCartActions module deals with other tasks for shopping. - -Modularization is more convenient for different components to import only required actions. - -You may import action module into action module for reusability. - -```javascript -// errorActions.js -export const setError = ({dispatch}, error) => { - dispatch('SET_ERROR', error) -} -export const showError = ({dispatch}) => { - dispatch('SET_ERROR_VISIBLE', true) -} -export const hideError = ({dispatch}) => { - dispatch('SET_ERROR_VISIBLE', false) -} -``` +And also in another action: -```javascript -// userActions.js -import {setError, showError} from './errorActions' - -export const login = ({dispatch}, username, password) => { - if (username && password) { - doLogin(username, password).done(res => { - dispatch('SET_USERNAME', res.username) - dispatch('SET_LOGGED_IN', true) - dispatch('SET_USER_INFO', res) - }).fail(error => { - dispatch('SET_INVALID_LOGIN') - setError({dispatch}, error) - showError({dispatch}) +``` js +actions: { + // ... + actionB ({ dispatch, commit }) { + return dispatch('actionA').then(() => { + commit('someOtherMutation') }) } } - ``` -While calling actions from another module, or while calling another action in the same module, remember that actions take a store instance as its first argument, so the action called inside another action should be passed through the first argument for the caller. - -If you write the action with ES6 destructuring style, make sure that the first argument of the caller action covers all the properties and methods of both actions. For example, only *dispatch* is used in the caller action and *state*, *watch* are used in the called action, all the *dispatch*, *state* and *watch* should be presented in the caller first formal argument like this: - -```javascript -import {callee} from './anotherActionModule' +Finally, if we make use of [async / await](https://tc39.github.io/ecmascript-asyncawait/), a JavaScript feature landing very soon, we can compose our actions like this: -export const caller = ({dispatch, state, watch}) => { - dispatch('MUTATION_1') - callee({state, watch}) +``` js +// assuming getData() and getOtherData() return Promises + +actions: { + async actionA ({ commit }) { + commit('gotData', await getData()) + }, + async actionB ({ dispatch, commit }) { + await dispatch('actionA') // wait for actionA to finish + commit('gotOtherData', await getOtherData()) + } } ``` -Otherwise, you should use the old-fashioned function syntax: - -```javascript -import {callee} from './anotherActionModule' - -export const caller = (store) => { - store.dispatch('MUTATION_1') - callee(store) -} -``` +> It's possible for a `store.dispatch` to trigger multiple action handlers in different modules. In such a case the returned value will be a Promise that resolves when all triggered handlers have been resolved. From fc4fc68a96036b1e29e728f3ad75832e1cd4ee2d Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 10:26:30 +0900 Subject: [PATCH 24/80] Copy docs/ja/actions.md from 1.0 --- docs/ja/actions.md | 200 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/docs/ja/actions.md b/docs/ja/actions.md index b5e9a2ebe..278c998b9 100644 --- a/docs/ja/actions.md +++ b/docs/ja/actions.md @@ -173,3 +173,203 @@ actions: { ``` > It's possible for a `store.dispatch` to trigger multiple action handlers in different modules. In such a case the returned value will be a Promise that resolves when all triggered handlers have been resolved. + +# アクション + +> Vuex のアクションは純粋な Flux の定義では実際には "アクションクリエーター (action creators)" ですが、その用語は有用さよりも混乱を生み出していると考えられます。 + +アクションはミューテーションをディスパッチする関数です。慣習として、 Vuex のアクションは常に1番目の引数にストアのインスタンスを受け取ることが期待され、2番目以降にはオプショナルな追加の引数が続きます。 + +``` js +// 最も単純なアクション +function increment (store) { + store.dispatch('INCREMENT') +} + +// 追加の引数を持つアクション +// ES2015 の引数分割束縛(argument destructuring)を使用しています +function incrementBy ({ dispatch }, amount) { + dispatch('INCREMENT', amount) +} +``` + +これは、なぜ単純に直接ミューテーションをディスパッチしないのかと、一見馬鹿げて見えるかもしれません。**ミューテーションは同期的でなければならない** というのを覚えていますか? アクションはそうではありません。アクションの中では **非同期** の操作をおこなうことができます。 + +``` js +function incrementAsync ({ dispatch }) { + setTimeout(() => { + dispatch('INCREMENT') + }, 1000) +} +``` + +より実践的な例として、ショッピングカートをチェックアウトするアクションを挙げます。このアクションは **非同期な API の呼び出し** と、**複数のミューテーションのディスパッチ** をします。 + +``` js +function checkout ({ dispatch, state }, products) { + // 現在のカート内の商品を保存します + const savedCartItems = [...state.cart.added] + // チェックアウトのリクエストを送信し、 + // 楽観的にカート内をクリアします + dispatch(types.CHECKOUT_REQUEST) + // shop API は成功時のコールバックと失敗時のコールバックを受け取ります + shop.buyProducts( + products, + // 成功処理 + () => dispatch(types.CHECKOUT_SUCCESS), + // 失敗処理 + () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) + ) +} +``` + +非同期 API 呼び出しの結果は、アクションの返り値やコールバックから受け取るのではなく、同様にミューテーションをディスパッチすることで扱っていることに注意してください。これは **アクションの呼び出しによって生み出される唯一の副作用はミューテーションのディスパッチであるべき** だという経験則です。 + +### コンポーネント内でのアクション呼び出し + +アクション関数はストアのインスタンスへの参照無しに直接呼び出すことができないということに気づいているかもしれません。技術的に、メソッド内で `action(this.$store)` とアクションを呼び出すことはできます。しかし、ストアを "束縛した" 版のアクションをコンポーネントのメソッドとして呼び出すことができればより良いですし、テンプレートの中から簡単に参照することができるようになります。これは `vuex.actions` オプションを使うことで実現できます。 + +``` js +// コンポーネント内 +import { incrementBy } from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, // ステートのゲッター + actions: { + incrementBy // ES6 オブジェクトリテラル省略記法(object literal shorthand)、同じ名前で束縛します + } + } +}) +``` + +上記のコードは元の `incrementBy` アクションをコンポーネントのストアインスタンスへと束縛し、それをコンポーネントのインスタンスメソッド `vm.incrementBy` として追加しています。`vm.incrementBy` に与えられた任意の引数は、第1引数にストアが渡されている状態で元のアクション関数へと渡されます。つまり以下のように呼ばれます。 + +``` js +vm.incrementBy(1) +``` + +これは以下と同様です。 + +``` js +incrementBy(vm.$store, 1) +``` + +このようにする利点はコンポーネントのテンプレートの中でより簡単にそれを束縛することができるという点です。 + +``` html + +``` + +アクションを束縛するときに、明示的に異なるメソッド名を使うこともできます。 + +``` js +// コンポーネント内 +import { incrementBy } from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, + actions: { + plus: incrementBy // 異なる名前で束縛します + } + } +}) +``` + +このようにすることで、アクションは `vm.incrementBy` ではなく、 `vm.plus` と束縛されるでしょう。 + +### インラインアクション + +もしアクションがコンポーネント特有のものであれば、それを単純にインラインで定義することもできます。 + +``` js +const vm = new Vue({ + vuex: { + getters: { ... }, + actions: { + plus: ({ dispatch }) => dispatch('INCREMENT') + } + } +}) +``` + +### すべてのアクションの束縛 + +もし、単純にすべての共通のアクションを束縛したいのであれば、以下のように書くことができます。 + +``` js +import * as actions from './actions' + +const vm = new Vue({ + vuex: { + getters: { ... }, + actions // すべてのアクションを束縛 + } +}) +``` + +### モジュール内でアクションをアレンジする + +大抵の大きなアプリケーションでは、アクションは様々な目的にあわせてグループやモジュール内でアレンジされるべきでしょう。例えば、userActions モジュールはユーザーの登録、ログイン、ログアウトなどの処理を行い、その一方で、shoppingCartActions モジュールは買い物のためのその他のタスクを処理します。 + +モジュール化をすることで、様々なコンポーネントで必要とされている最小限のアクションのインポートがよりしやすくなります。 + +再利用のためにあるアクションモジュールを別のアクションモジュールにインポートする場合もあるかもしれません。 + +```javascript +// errorActions.js +export const setError = ({dispatch}, error) => { + dispatch('SET_ERROR', error) +} +export const showError = ({dispatch}) => { + dispatch('SET_ERROR_VISIBLE', true) +} +export const hideError = ({dispatch}) => { + dispatch('SET_ERROR_VISIBLE', false) +} +``` + +```javascript +// userActions.js +import {setError, showError} from './errorActions' + +export const login = ({dispatch}, username, password) => { + if (username && password) { + doLogin(username, password).done(res => { + dispatch('SET_USERNAME', res.username) + dispatch('SET_LOGGED_IN', true) + dispatch('SET_USER_INFO', res) + }).fail(error => { + dispatch('SET_INVALID_LOGIN') + setError({dispatch}, error) + showError({dispatch}) + }) + } +} + +``` + +アクションを別のモジュールから呼び出す時や、同一モジュール内の別のアクションを呼び出す時は、アクションが第1引数にストアのインスタンスをとることを覚えておきましょう。すなわち、アクション内で呼び出されるアクションには、呼び出し元が受け取った第1引数をそのまま渡すべきです。 + +もしアクションを ES6 の分割束縛(destructuring)スタイルで書いているのであれば、呼び出し元のアクションの第1引数は、両方のアクションが必要とするすべてのプロパティ、および、メソッドをカバーする必要があります。例えば、呼び出し元のアクションが *dispatch* のみを使用し、呼び出し先のアクションが *state* と *watch* を使用している時、呼び出し元の第1引数には、以下のように *dispatch*, *state*, *watch* のすべてを渡すべきです。 + +```javascript +import {callee} from './anotherActionModule' + +export const caller = ({dispatch, state, watch}) => { + dispatch('MUTATION_1') + callee({state, watch}) +} +``` + +そうでなければ、旧式の関数の書き方をするべきです。 + +```javascript +import {callee} from './anotherActionModule' + +export const caller = (store) => { + store.dispatch('MUTATION_1') + callee(store) +} +``` From 4b220d09899d73d9cb96221177910d8883216782 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Wed, 23 Nov 2016 11:04:10 +0900 Subject: [PATCH 25/80] Translate state --- docs/ja/intro.md | 4 +- docs/ja/state.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 docs/ja/state.md diff --git a/docs/ja/intro.md b/docs/ja/intro.md index 899b8375a..4da6804c7 100644 --- a/docs/ja/intro.md +++ b/docs/ja/intro.md @@ -59,6 +59,6 @@ Vuex の背景にある基本的なアイディアは、[Flux](https://facebook. Vuex は、共有状態の管理に役立ちますが、さらに概念やボイラープレートのコストがかかります。これは、短期的生産性と長期的生産性のトレードオフです。 -もし、あなたが大規模なSPAを構築することなく、Vuex を導入した場合、冗長で恐ろしいかんじになるかもしれません。そう感じることは全く普通です。もし、あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。シンプルな [グローバル イベント パス](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication) が必要なだけかもしれません。しかし、中規模から大規模のSPAを構築する場合は、Vue コンポーネント以外のどうやってうまく扱うか考える絶好の機会です。Vuex は自然な次のステップとなるでしょう。これは Redux の作者、Dan Abramov からの良い引用です: +もし、あなたが大規模なSPAを構築することなく、Vuex を導入した場合、冗長で恐ろしいかんじになるかもしれません。そう感じることは全く普通です。もし、あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。シンプルな [グローバル イベント バス](http://vuejs.org/guide/components.html#Non-Parent-Child-Communication) が必要なだけかもしれません。しかし、中規模から大規模のSPAを構築する場合は、Vue コンポーネント以外のどうやってうまく扱うか考える絶好の機会です。Vuex は自然な次のステップとなるでしょう。これは Redux の作者、Dan Abramov からの良い引用です: -> Flux ライブラリは眼鏡のようなものです: それらが必要になったときに知ることになります。 +> Flux ライブラリは眼鏡のようなものです: それらが必要になったときに知るのです。 diff --git a/docs/ja/state.md b/docs/ja/state.md new file mode 100644 index 000000000..143bd3594 --- /dev/null +++ b/docs/ja/state.md @@ -0,0 +1,106 @@ +# ステート + +### 単一ステートツリー + +Vuex は**単一ステートツリー (single state tree)**を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。単一ステートツリーは状態の特定の部分を見つけること、デバッグのために現在のアプリケーションの状態のスナップショットを撮ることを容易にします。 + +単一ステートツリーはモジュール性とコンフリクト(競合)しません。以降の章で、アプリケーションの状態とミューテーション(変更)をサブモジュールに分割する方法について説明します。 + +### Vuex の状態を Vue コンポーネントに入れる + +ストアにある状態を Vue コンポーネント に表示するにはどうすればよいのでしょう? Vuex ストア はリアクティブなので、ストアから状態を"取り出す"一番シンプルな方法は、単純にいくつかのストアの状態を [算出プロパティ](https://jp.vuejs.org/guide/computed.html) で返すことです。 + +``` js +// Counter コンポーネントをつくってみましょう +const Counter = { + template: `
{{ count }}
`, + computed: { + count: function() { + return store.state.count + } + } +} +``` + +`store.state.count` が変わるたび、算出プロパティの再評価が発生し、関連した DOM の更新をトリガーします。 + +しかし、このパターンでは、コンポーネントがグローバルストアシングルトンに依存してしまいます。 モジュールシステムを使っているとき、ストアの状態を使っているすべてのコンポーネントでインポートが必要です。また、コンポーネントのテストのときにモック化が必要となります。 + +Vuex は、ルートコンポーネントに `store` オプションを指定することで (これは、 `Vue.use(Vuex)` で有効にできます)、すべての子コンポーネントにストアを "挿入" する機構を提供しています: + + ``` js + const app = new Vue({ + el: '#app', + // "store" オプションで指定されたストアは、全ての子コンポーネントに注入されます + store, + components: { Counter }, + template: ` +
+ +
+ ` + }) + ``` + + ルートインスタンスに `store` オプションを渡すことで、渡されたストアをルートの全ての子コンポーネントに注入します。これは `this.$store` で各コンポーネントから参照することができます。 `Counter` の実装を変更しましょう: + + ``` js + const Counter = { + template: `
{{ count }}
`, + computed: { + count: function() { + return this.$store.state.count + } + } + } + ``` + +### `mapState`  ヘルパー + +コンポーネントが複数のストアのステートプロパティやゲッターを必要としているとき、これらすべてにおいて、算出プロパティを宣言することは繰り返しで冗長です。これに対処するため、算出ゲッター関数を生成し、いくつかのキーストロークを節約するのに役立つ `mapState` ヘルパーを使うことができます: + +```js +// スタンドアローンビルドでは、ヘルパーは Vuex.mapState として公開されています +import { mapState } from 'vuex' + +export default { + // ... + computed: mapState({ + // アロー関数は、コードをとても簡潔にできます! + count: state => state.count, + // 文字列を渡すことは、`state => state.count` と同じです + countAlias: 'count', + // `this` からローカルステートを参照するときは、通常の関数を使わなければいけません + countPlusLocalState (state) { + return state.count + this.localCount + } + }) +} +``` + +マップされた算出プロパティの名前がステートサブツリーの名前と同じ場合は、文字列配列を `mapState` に渡すこともできます。 + +```js +computed: mapState([ + // map this.count to store.state.count + 'count' +]) +``` + +### オブジェクトスプレッド演算子 + +`mapState` はオブジェクトを返すことに注意しましょう。どうやって、他のローカル算出プロパティと組み合わせるのでしょうか? 通常、最終的にひとつのオブジェクトを `computed` に渡せるように、複数のオブジェクトをひとつにマージするユーティリティを使わなければいけません。しかし、[オブジェクトスプレッド演算子](https://github.com/sebmarkbage/ecmascript-rest-spread) (ECMAScript プロポーサルの state-3 です) で、シンタックスをかなり単純にできます: + +```js +computed: { + localComputed () { /* ... */ }. + // オブジェクトスプレット演算子で、外のオブジェクトとこのオブジェクトを混ぜる + ...mapState({ + // ... + }) +} +``` + +### コンポーネントはまだローカルステートを持つことできる + +Vuex を使うということは、**全て**の状態を Vuex の中に置くべき、というわけではありません。多くの状態を Vuex に置くことで、状態の変更がさらに明示的、デバッグ可能になりますが、ときには、それがコードを冗長でまわりくどいものにします。状態の一部がひとつのコンポーネントだけに属している場合は、それをローカルの状態として残しておくとよいでしょう。あなたは、トレードオフを考慮した上で、あなたのアプリの開発ニーズに合った決定をすべきです。 \ No newline at end of file From d381f87d74dee08586ff10b8cd9346fcb4e882ec Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 11:02:49 +0900 Subject: [PATCH 26/80] Translate dispatching actions --- docs/ja/actions.md | 49 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/ja/actions.md b/docs/ja/actions.md index 278c998b9..106fa288b 100644 --- a/docs/ja/actions.md +++ b/docs/ja/actions.md @@ -1,11 +1,11 @@ -# Actions +# アクション -Actions are similar to mutations, the difference being that: +アクションはミューテーションと似ていますが、下記の点で異なります: -- Instead of mutating the state, actions commit mutations. -- Actions can contain arbitrary asynchronous operations. +- アクションは、状態を変更するのではなく、ミューテーションをコミットします。 +- アクションは任意の非同期処理を含むことができます。 -Let's register a simple action: +シンプルなアクションを登録してみましょう: ``` js const store = new Vuex.Store({ @@ -25,9 +25,9 @@ const store = new Vuex.Store({ }) ``` -Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call `context.commit` to commit a mutation, or access the state and getters via `context.state` and `context.getters`. We will see why this context object is not the store instance itself when we introduce [Modules](modules.md) later. +アクションハンドラはストアインスタンスのメソッドやプロパティのセットと同じものを呼び出せる、コンテキストオブジェクトを受け取ります。したがって `context.commit` を呼び出すことでミューテーションをコミットできます。あるいは `context.state` や `context.getters` で、状態やゲッターにアクセスできます。なぜコンテキストオブジェクトがストアインスタンスそのものではないのかは、後ほど [モジュール](modules.md) で説明します。 -In practice, we often use ES2015 [argument destructuring](https://github.com/lukehoban/es6features#destructuring) to simplify the code a bit (especially when we need to call `commit` multiple times): +実際にはコードを少しシンプルにするために ES2015 の [引数分割束縛(argument destructuring)](https://github.com/lukehoban/es6features#destructuring) がよく使われます(特に `commit` を複数回呼び出す必要があるとき): ``` js actions: { @@ -37,15 +37,15 @@ actions: { } ``` -### Dispatching Actions +### アクションのディスパッチ -Actions are triggered with the `store.dispatch` method: +アクションは `store.dispatch` がトリガーとなって実行されます: ``` js store.dispatch('increment') ``` -This may look dumb at first sight: if we want to increment the count, why don't we just call `store.commit('increment')` directly? Well, remember that **mutations must be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: +これは一見ばかげて見えるかもしれません。つまり、カウントをインクリメントしたいときに、どうして直接 `store.commit('increment')` を呼び出してミューテーションをコミットしないのか、と。**ミューテーションは同期的でなければならない** というのを覚えていますか?アクションはそうではありません。アクションの中では **非同期** の操作をおこなうことができます。 ``` js actions: { @@ -57,48 +57,47 @@ actions: { } ``` -Actions support the same payload format and object-style dispatch: +アクションはペイロード形式とオブジェクトスタイルのディスパッチをサポートします: ``` js -// dispatch with a payload +// ペイロードを使ってディスパッチする store.dispatch('incrementAsync', { amount: 10 }) -// dispatch with an object +// オブジェクトを使ってディスパッチする store.dispatch({ type: 'incrementAsync', amount: 10 }) ``` -A more practical example of real-world actions would be an action to checkout a shopping cart, which involves **calling an async API** and **committing multiple mutations**: +より実践的な例として、ショッピングカートをチェックアウトするアクションを挙げます。このアクションは **非同期な API の呼び出し** と、**複数のミューテーションのコミット** をします: ``` js actions: { checkout ({ commit, state }, payload) { - // save the items currently in the cart + // 現在のカート内の商品を保存する const savedCartItems = [...state.cart.added] - // send out checkout request, and optimistically - // clear the cart + // チェックアウトのリクエストを送信し、楽観的にカート内をクリアする commit(types.CHECKOUT_REQUEST) - // the shop API accepts a success callback and a failure callback + // shop API は成功時のコールバックと失敗時のコールバックを受け取る shop.buyProducts( products, - // handle success + // 成功時の処理 () => commit(types.CHECKOUT_SUCCESS), - // handle failure + // 失敗時の処理 () => commit(types.CHECKOUT_FAILURE, savedCartItems) ) } } ``` -Note we are performing a flow of asynchronous operations, and recording the side effects (state mutations) of the action by committing them. +一連の非同期の処理を実行しつつ、ミューテーションのコミットによってのみ副作用(状態の変更)を与えていることに注意してください。 -### Dispatching Actions in Components +### コンポーネント内でのアクションのディスパッチ -You can dispatch actions in components with `this.$store.dispatch('xxx')`, or use the `mapActions` helper which maps component methods to `store.dispatch` calls (requires root `store` injection): +`this.$store.dispatch('xxx')` でコンポーネント内でアクションをディスパッチできます。あるいはコンポーネントのメソッドを `store.dispatch` にマッピングする `mapActions` ヘルパーを使うこともできます(ルートの `store` が必要です): ``` js import { mapActions } from 'vuex' @@ -107,10 +106,10 @@ export default { // ... methods: { ...mapActions([ - 'increment' // map this.increment() to this.$store.dispatch('increment') + 'increment' // this.increment() を this.$store.dispatch('increment') にマッピングする ]), ...mapActions({ - add: 'increment' // map this.add() to this.$store.dispatch('increment') + add: 'increment' // this.add() を this.$store.dispatch('increment') にマッピングする }) } } From d245499a4d8711d33c7280966f8533c405a0acac Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 11:34:52 +0900 Subject: [PATCH 27/80] Translate actions.md --- docs/ja/actions.md | 216 ++------------------------------------------- 1 file changed, 8 insertions(+), 208 deletions(-) diff --git a/docs/ja/actions.md b/docs/ja/actions.md index 106fa288b..d1a790b63 100644 --- a/docs/ja/actions.md +++ b/docs/ja/actions.md @@ -115,11 +115,11 @@ export default { } ``` -### Composing Actions +### アクションを構成する -Actions are often asynchronous, so how do we know when an action is done? And more importantly, how can we compose multiple actions together to handle more complex async flows? +アクションはしばしば非同期処理を行いますが、アクションが完了したことをどうやって知れば良いのでしょう?そしてもっと重要なことは、もっと複雑な非同期処理を取り扱うために、どうやって複数のアクションを構成させるかということです。 -The first thing to know is that `store.dispatch` returns the value returned by the triggered action handler, so you can return a Promise in an action: +まず知っておくべきことは `store.dispatch` は、トリガーされたアクションハンドラによって返された値を、戻り値として返すということです。したがってアクションの中で Promise を返すようにできます: ``` js actions: { @@ -134,7 +134,7 @@ actions: { } ``` -Now you can do: +すると下記のようにできます: ``` js store.dispatch('actionA').then(() => { @@ -142,7 +142,7 @@ store.dispatch('actionA').then(() => { }) ``` -And also in another action: +また別のアクションで下記のように書くと: ``` js actions: { @@ -155,7 +155,7 @@ actions: { } ``` -Finally, if we make use of [async / await](https://tc39.github.io/ecmascript-asyncawait/), a JavaScript feature landing very soon, we can compose our actions like this: +最終的には [async / await](https://tc39.github.io/ecmascript-asyncawait/) を使うと、JavaScript はとても素早くランディングを計算でき、複数のアクションを以下のように構成できます: ``` js // assuming getData() and getOtherData() return Promises @@ -165,210 +165,10 @@ actions: { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { - await dispatch('actionA') // wait for actionA to finish + await dispatch('actionA') // actionA が完了するのを待機する commit('gotOtherData', await getOtherData()) } } ``` -> It's possible for a `store.dispatch` to trigger multiple action handlers in different modules. In such a case the returned value will be a Promise that resolves when all triggered handlers have been resolved. - -# アクション - -> Vuex のアクションは純粋な Flux の定義では実際には "アクションクリエーター (action creators)" ですが、その用語は有用さよりも混乱を生み出していると考えられます。 - -アクションはミューテーションをディスパッチする関数です。慣習として、 Vuex のアクションは常に1番目の引数にストアのインスタンスを受け取ることが期待され、2番目以降にはオプショナルな追加の引数が続きます。 - -``` js -// 最も単純なアクション -function increment (store) { - store.dispatch('INCREMENT') -} - -// 追加の引数を持つアクション -// ES2015 の引数分割束縛(argument destructuring)を使用しています -function incrementBy ({ dispatch }, amount) { - dispatch('INCREMENT', amount) -} -``` - -これは、なぜ単純に直接ミューテーションをディスパッチしないのかと、一見馬鹿げて見えるかもしれません。**ミューテーションは同期的でなければならない** というのを覚えていますか? アクションはそうではありません。アクションの中では **非同期** の操作をおこなうことができます。 - -``` js -function incrementAsync ({ dispatch }) { - setTimeout(() => { - dispatch('INCREMENT') - }, 1000) -} -``` - -より実践的な例として、ショッピングカートをチェックアウトするアクションを挙げます。このアクションは **非同期な API の呼び出し** と、**複数のミューテーションのディスパッチ** をします。 - -``` js -function checkout ({ dispatch, state }, products) { - // 現在のカート内の商品を保存します - const savedCartItems = [...state.cart.added] - // チェックアウトのリクエストを送信し、 - // 楽観的にカート内をクリアします - dispatch(types.CHECKOUT_REQUEST) - // shop API は成功時のコールバックと失敗時のコールバックを受け取ります - shop.buyProducts( - products, - // 成功処理 - () => dispatch(types.CHECKOUT_SUCCESS), - // 失敗処理 - () => dispatch(types.CHECKOUT_FAILURE, savedCartItems) - ) -} -``` - -非同期 API 呼び出しの結果は、アクションの返り値やコールバックから受け取るのではなく、同様にミューテーションをディスパッチすることで扱っていることに注意してください。これは **アクションの呼び出しによって生み出される唯一の副作用はミューテーションのディスパッチであるべき** だという経験則です。 - -### コンポーネント内でのアクション呼び出し - -アクション関数はストアのインスタンスへの参照無しに直接呼び出すことができないということに気づいているかもしれません。技術的に、メソッド内で `action(this.$store)` とアクションを呼び出すことはできます。しかし、ストアを "束縛した" 版のアクションをコンポーネントのメソッドとして呼び出すことができればより良いですし、テンプレートの中から簡単に参照することができるようになります。これは `vuex.actions` オプションを使うことで実現できます。 - -``` js -// コンポーネント内 -import { incrementBy } from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, // ステートのゲッター - actions: { - incrementBy // ES6 オブジェクトリテラル省略記法(object literal shorthand)、同じ名前で束縛します - } - } -}) -``` - -上記のコードは元の `incrementBy` アクションをコンポーネントのストアインスタンスへと束縛し、それをコンポーネントのインスタンスメソッド `vm.incrementBy` として追加しています。`vm.incrementBy` に与えられた任意の引数は、第1引数にストアが渡されている状態で元のアクション関数へと渡されます。つまり以下のように呼ばれます。 - -``` js -vm.incrementBy(1) -``` - -これは以下と同様です。 - -``` js -incrementBy(vm.$store, 1) -``` - -このようにする利点はコンポーネントのテンプレートの中でより簡単にそれを束縛することができるという点です。 - -``` html - -``` - -アクションを束縛するときに、明示的に異なるメソッド名を使うこともできます。 - -``` js -// コンポーネント内 -import { incrementBy } from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, - actions: { - plus: incrementBy // 異なる名前で束縛します - } - } -}) -``` - -このようにすることで、アクションは `vm.incrementBy` ではなく、 `vm.plus` と束縛されるでしょう。 - -### インラインアクション - -もしアクションがコンポーネント特有のものであれば、それを単純にインラインで定義することもできます。 - -``` js -const vm = new Vue({ - vuex: { - getters: { ... }, - actions: { - plus: ({ dispatch }) => dispatch('INCREMENT') - } - } -}) -``` - -### すべてのアクションの束縛 - -もし、単純にすべての共通のアクションを束縛したいのであれば、以下のように書くことができます。 - -``` js -import * as actions from './actions' - -const vm = new Vue({ - vuex: { - getters: { ... }, - actions // すべてのアクションを束縛 - } -}) -``` - -### モジュール内でアクションをアレンジする - -大抵の大きなアプリケーションでは、アクションは様々な目的にあわせてグループやモジュール内でアレンジされるべきでしょう。例えば、userActions モジュールはユーザーの登録、ログイン、ログアウトなどの処理を行い、その一方で、shoppingCartActions モジュールは買い物のためのその他のタスクを処理します。 - -モジュール化をすることで、様々なコンポーネントで必要とされている最小限のアクションのインポートがよりしやすくなります。 - -再利用のためにあるアクションモジュールを別のアクションモジュールにインポートする場合もあるかもしれません。 - -```javascript -// errorActions.js -export const setError = ({dispatch}, error) => { - dispatch('SET_ERROR', error) -} -export const showError = ({dispatch}) => { - dispatch('SET_ERROR_VISIBLE', true) -} -export const hideError = ({dispatch}) => { - dispatch('SET_ERROR_VISIBLE', false) -} -``` - -```javascript -// userActions.js -import {setError, showError} from './errorActions' - -export const login = ({dispatch}, username, password) => { - if (username && password) { - doLogin(username, password).done(res => { - dispatch('SET_USERNAME', res.username) - dispatch('SET_LOGGED_IN', true) - dispatch('SET_USER_INFO', res) - }).fail(error => { - dispatch('SET_INVALID_LOGIN') - setError({dispatch}, error) - showError({dispatch}) - }) - } -} - -``` - -アクションを別のモジュールから呼び出す時や、同一モジュール内の別のアクションを呼び出す時は、アクションが第1引数にストアのインスタンスをとることを覚えておきましょう。すなわち、アクション内で呼び出されるアクションには、呼び出し元が受け取った第1引数をそのまま渡すべきです。 - -もしアクションを ES6 の分割束縛(destructuring)スタイルで書いているのであれば、呼び出し元のアクションの第1引数は、両方のアクションが必要とするすべてのプロパティ、および、メソッドをカバーする必要があります。例えば、呼び出し元のアクションが *dispatch* のみを使用し、呼び出し先のアクションが *state* と *watch* を使用している時、呼び出し元の第1引数には、以下のように *dispatch*, *state*, *watch* のすべてを渡すべきです。 - -```javascript -import {callee} from './anotherActionModule' - -export const caller = ({dispatch, state, watch}) => { - dispatch('MUTATION_1') - callee({state, watch}) -} -``` - -そうでなければ、旧式の関数の書き方をするべきです。 - -```javascript -import {callee} from './anotherActionModule' - -export const caller = (store) => { - store.dispatch('MUTATION_1') - callee(store) -} -``` +> `store.dispatch` で異なるモジュール内の複数のアクションハンドラをトリガーすることができます。そのようなケースでは、全てのトリガーされたハンドラがリゾルブされたときにリゾルブする Promise が戻り値として返ってくることになります。 From 575fe13fe20c62ef9fd75a66f2d0c40e80556986 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 11:55:45 +0900 Subject: [PATCH 28/80] Copy docs/en/modules.md to docs/ja/modules.md --- docs/ja/modules.md | 138 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/ja/modules.md diff --git a/docs/ja/modules.md b/docs/ja/modules.md new file mode 100644 index 000000000..51ee44bf7 --- /dev/null +++ b/docs/ja/modules.md @@ -0,0 +1,138 @@ +# Modules + +Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated. + +To help with that, Vuex allows us to divide our store into **modules**. Each module can contain its own state, mutations, actions, getters, and even nested modules - it's fractal all the way down: + +``` js +const moduleA = { + state: { ... }, + mutations: { ... }, + actions: { ... }, + getters: { ... } +} + +const moduleB = { + state: { ... }, + mutations: { ... }, + actions: { ... } +} + +const store = new Vuex.Store({ + modules: { + a: moduleA, + b: moduleB + } +}) + +store.state.a // -> moduleA's state +store.state.b // -> moduleB's state +``` + +### Module Local State + +Inside a module's mutations and getters, The first argument received will be **the module's local state**. + +``` js +const moduleA = { + state: { count: 0 }, + mutations: { + increment: (state) { + // state is the local module state + state.count++ + } + }, + + getters: { + doubleCount (state) { + return state.count * 2 + } + } +} +``` + +Similarly, inside module actions, `context.state` will expose the local state, and root state will be exposed as `context.rootState`: + +``` js +const moduleA = { + // ... + actions: { + incrementIfOdd ({ state, commit }) { + if (state.count % 2 === 1) { + commit('increment') + } + } + } +} +``` + +Also, inside module getters, the root state will be exposed as their 3rd argument: + +``` js +const moduleA = { + // ... + getters: { + sumWithRootCount (state, getters, rootState) { + return state.count + rootState.count + } + } +} +``` + +### Namespacing + +Note that actions, mutations and getters inside modules are still registered under the **global namespace** - this allows multiple modules to react to the same mutation/action type. You can namespace the module assets yourself to avoid name clashing by prefixing or suffixing their names. And you probably should if you are writing a reusable Vuex module that will be used in unknown environments. For example, we want to create a `todos` module: + +``` js +// types.js + +// define names of getters, actions and mutations as constants +// and they are prefixed by the module name `todos` +export const DONE_COUNT = 'todos/DONE_COUNT' +export const FETCH_ALL = 'todos/FETCH_ALL' +export const TOGGLE_DONE = 'todos/TOGGLE_DONE' +``` + +``` js +// modules/todos.js +import * as types from '../types' + +// define getters, actions and mutations using prefixed names +const todosModule = { + state: { todos: [] }, + + getters: { + [types.DONE_COUNT] (state) { + // ... + } + }, + + actions: { + [types.FETCH_ALL] (context, payload) { + // ... + } + }, + + mutations: { + [types.TOGGLE_DONE] (state, payload) { + // ... + } + } +} +``` + +### Dynamic Module Registration + +You can register a module **after** the store has been created with the `store.registerModule` method: + +``` js +store.registerModule('myModule', { + // ... +}) +``` + +The module's state will be exposed as `store.state.myModule`. + +Dynamic module registration makes it possible for other Vue plugins to also leverage Vuex for state management by attaching a module to the application's store. For example, the [`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) library integrates vue-router with vuex by managing the application's route state in a dynamically attached module. + +You can also remove a dynamically registered module with `store.unregisterModule(moduleName)`. Note you cannot remove static modules (declared at store creation) with this method. From cea0371161dbed95cf3ae11189900c1069d98d70 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 12:45:23 +0900 Subject: [PATCH 29/80] Translate modules.md --- docs/ja/modules.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/ja/modules.md b/docs/ja/modules.md index 51ee44bf7..934b5e824 100644 --- a/docs/ja/modules.md +++ b/docs/ja/modules.md @@ -1,8 +1,10 @@ # Modules -Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated. +単一ステートツリーを使うため、アプリケーションの全ての状態は、一つの大きなストアオブジェクトに内包されます。しかしながら、アプリケーションが大きくなるにつれて、ストアオブジェクトは膨れ上がってきます。 -To help with that, Vuex allows us to divide our store into **modules**. Each module can contain its own state, mutations, actions, getters, and even nested modules - it's fractal all the way down: +そのような場合に役立てるため Vuex ではストアを **モジュール** に分割できるようになっています。 + +それぞれのモジュールは、ステート、ミューテーション、アクション、ゲッター、モジュールさえも内包できます(モジュールをネストできます)- トップからボトムまでフラクタル構造です: ``` js const moduleA = { @@ -29,16 +31,16 @@ store.state.a // -> moduleA's state store.state.b // -> moduleB's state ``` -### Module Local State +### モジュールのローカルステート -Inside a module's mutations and getters, The first argument received will be **the module's local state**. +モジュールのミューテーションやゲッターの中では、渡される第1引数は **モジュールのローカルステート** です。 ``` js const moduleA = { state: { count: 0 }, mutations: { increment: (state) { - // state is the local module state + // state はモジュールのローカルステート state.count++ } }, @@ -51,7 +53,7 @@ const moduleA = { } ``` -Similarly, inside module actions, `context.state` will expose the local state, and root state will be exposed as `context.rootState`: +同様に、モジュールのアクションの中では `context.state` はローカルステートにアクセスでき、ルートのステートは `context.rootState` でアクセスできます: ``` js const moduleA = { @@ -66,7 +68,7 @@ const moduleA = { } ``` -Also, inside module getters, the root state will be exposed as their 3rd argument: +また、モジュールのゲッターの中では、ルートのステートは第3引数でアクセスできます: ``` js const moduleA = { @@ -79,15 +81,15 @@ const moduleA = { } ``` -### Namespacing +### 名前空間 -Note that actions, mutations and getters inside modules are still registered under the **global namespace** - this allows multiple modules to react to the same mutation/action type. You can namespace the module assets yourself to avoid name clashing by prefixing or suffixing their names. And you probably should if you are writing a reusable Vuex module that will be used in unknown environments. For example, we want to create a `todos` module: +モジュール内部のアクションやミューテーション、ゲッターは依然として **グローバル名前空間** の下に登録されることに注意してください。そのため、複数のモジュールが同一のミューテーションやアクションタイプに反応してしまいます。接頭語や接尾語を付けることで名前の衝突を回避できますし、再利用可能でかつどこで使われるか分からない Vuex のモジュールを書いているのならば、そうすべきです。例えば `todos` モジュールを作りたいときは以下のようにします: ``` js // types.js -// define names of getters, actions and mutations as constants -// and they are prefixed by the module name `todos` +// ゲッター、アクション、ミューテーションの名前を定数として定義し、 +// それらにモジュール名である `todos` を接頭語として付ける export const DONE_COUNT = 'todos/DONE_COUNT' export const FETCH_ALL = 'todos/FETCH_ALL' export const TOGGLE_DONE = 'todos/TOGGLE_DONE' @@ -97,7 +99,7 @@ export const TOGGLE_DONE = 'todos/TOGGLE_DONE' // modules/todos.js import * as types from '../types' -// define getters, actions and mutations using prefixed names +// 接頭語を付けたゲッター、アクション、ミューテーションを定義する const todosModule = { state: { todos: [] }, @@ -121,9 +123,9 @@ const todosModule = { } ``` -### Dynamic Module Registration +### 動的にモジュールを登録する -You can register a module **after** the store has been created with the `store.registerModule` method: +ストアが作られた**後**に `store.registerModule` メソッドを使って、モジュールを登録できます: ``` js store.registerModule('myModule', { @@ -131,8 +133,8 @@ store.registerModule('myModule', { }) ``` -The module's state will be exposed as `store.state.myModule`. +モジュールのステートには `store.state.myModule` でアクセスします。 -Dynamic module registration makes it possible for other Vue plugins to also leverage Vuex for state management by attaching a module to the application's store. For example, the [`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) library integrates vue-router with vuex by managing the application's route state in a dynamically attached module. +動的なモジュール登録があることで、他の Vue プラグインが、モジュールをアプリケーションのストアに付属させることで、状態の管理に Vuex を活用できることができます。例えば [`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) ライブラリは、動的に付属させたモジュール内部でアプリケーションのルートステートを管理することで vue-router と vuex を統合しています。 -You can also remove a dynamically registered module with `store.unregisterModule(moduleName)`. Note you cannot remove static modules (declared at store creation) with this method. +`store.unregisterModule(moduleName)` を呼び出せば、動的に登録したモジュールを削除できます。ただしストア作成(store creation)によって宣言された、静的なモジュールはこのメソッドで削除できないことに注意してください。 From e0927c11925200a32cc7cc48742dc3bdfbe564d4 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 13:24:34 +0900 Subject: [PATCH 30/80] Translate title of docs/ja/modules.md --- docs/ja/modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ja/modules.md b/docs/ja/modules.md index 934b5e824..c6d0210b4 100644 --- a/docs/ja/modules.md +++ b/docs/ja/modules.md @@ -1,4 +1,4 @@ -# Modules +# モジュール 単一ステートツリーを使うため、アプリケーションの全ての状態は、一つの大きなストアオブジェクトに内包されます。しかしながら、アプリケーションが大きくなるにつれて、ストアオブジェクトは膨れ上がってきます。 From b855aa86f2e64ca7de7152b010618deb4b33c60b Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 13:27:58 +0900 Subject: [PATCH 31/80] Copy docs/en/structure.md of 1.0 to docs/ja/structure.md --- docs/ja/structure.md | 136 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/ja/structure.md diff --git a/docs/ja/structure.md b/docs/ja/structure.md new file mode 100644 index 000000000..5c9fb1563 --- /dev/null +++ b/docs/ja/structure.md @@ -0,0 +1,136 @@ +# Application Structure + +Vuex doesn't really restrict how you structure your code. Rather, it enforces a set of high-level principles: + +1. Application state is held in the store, as a single object. + +2. The only way to mutate the state is by dispatching mutations on the store. + +3. Mutations must be synchronous, and the only side effects they produce should be mutating the state. + +4. We can expose a more expressive state mutation API by defining actions. Actions can encapsulate asynchronous logic such as data fetching, and the only side effects they produce should be dispatching mutations. + +5. Components use getters to retrieve state from the store, and call actions to mutate the state. + +The nice thing about Vuex mutations, actions and getters is that **they are all just functions**. As long as you follow these rules, it's up to you how to structure your project. However, it's nice to have some conventions so that you can instantly become familiar with another project that uses Vuex, so here are some recommended structures depending on the scale of your app. + +### Simple Project + +For a simple project, we can simply define the **store** and the **actions** in respective files: + +``` bash +. +├── index.html +├── main.js +├── components +│ ├── App.vue +│ └── ... +└── vuex + ├── store.js # exports the store (with initial state and mutations) + └── actions.js # exports all actions +``` + +For an actual example, check out the [Counter example](https://github.com/vuejs/vuex/tree/master/examples/counter) or the [TodoMVC example](https://github.com/vuejs/vuex/tree/master/examples/todomvc). + +Alternatively, you can also split out mutations into its own file. + +### Medium to Large Project + +For any non-trivial app, we probably want to further split Vuex-related code into multiple "modules" (roughly comparable to "stores" in vanilla Flux, and "reducers" in Redux), each dealing with a specific domain of our app. Each module would be managing a sub-tree of the state, exporting the initial state for that sub-tree and all mutations that operate on that sub-tree: + +``` bash +├── index.html +├── main.js +├── api +│ └── ... # abstractions for making API requests +├── components +│ ├── App.vue +│ └── ... +└── vuex + ├── actions.js # exports all actions + ├── store.js # where we assemble modules and export the store + ├── mutation-types.js # constants + └── modules + ├── cart.js # state and mutations for cart + └── products.js # state and mutations for products +``` + +A typical module looks like this: + +``` js +// vuex/modules/products.js +import { + RECEIVE_PRODUCTS, + ADD_TO_CART +} from '../mutation-types' + +// initial state +const state = { + all: [] +} + +// mutations +const mutations = { + [RECEIVE_PRODUCTS] (state, products) { + state.all = products + }, + + [ADD_TO_CART] (state, productId) { + state.all.find(p => p.id === productId).inventory-- + } +} + +export default { + state, + mutations +} +``` + +And in `vuex/store.js`, we "assemble" multiple modules together to create the Vuex instance: + +``` js +// vuex/store.js +import Vue from 'vue' +import Vuex from '../../../src' +// import parts from modules +import cart from './modules/cart' +import products from './modules/products' + +Vue.use(Vuex) + +export default new Vuex.Store({ + // combine sub modules + modules: { + cart, + products + } +}) +``` + +Here, `cart` module's initial state will be attached to the root state tree as `store.state.cart`. In addition, **all the mutations defined in a sub-module only receive the sub-state-tree they are associated with**. So mutations defined in the `cart` module will receive `store.state.cart` as their first argument. + +The root of the sub-state-tree is irreplaceable inside the module itself. For example this won't work: + +``` js +const mutations = { + SOME_MUTATION (state) { + state = { ... } + } +} +``` + +Instead, always store actual state as a property of the sub-tree root: + +``` js +const mutations = { + SOME_MUTATION (state) { + state.value = { ... } + } +} +``` + +Since all modules simply export objects and functions, they are quite easy to test and maintain, and can be hot-reloaded. You are also free to alter the patterns used here to find a structure that fits your preference. + +Note that we do not put actions into modules, because a single action may dispatch mutations that affect multiple modules. It's also a good idea to decouple actions from the state shape and the implementation details of mutations for better separation of concerns. If the actions file gets too large, we can turn it into a folder and split out the implementations of long async actions into individual files. + +For an example, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/master/examples/shopping-cart). From 4bd7b117363d03b1228611c34b5b1e7da322428d Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 13:29:25 +0900 Subject: [PATCH 32/80] Copy docs/en/structure.md of 2.0, understanding diff 1.0 and 2.0 --- docs/ja/structure.md | 134 +++++-------------------------------------- 1 file changed, 15 insertions(+), 119 deletions(-) diff --git a/docs/ja/structure.md b/docs/ja/structure.md index 5c9fb1563..2ba11e37b 100644 --- a/docs/ja/structure.md +++ b/docs/ja/structure.md @@ -2,135 +2,31 @@ Vuex doesn't really restrict how you structure your code. Rather, it enforces a set of high-level principles: -1. Application state is held in the store, as a single object. +1. Application-level state is centralized in the store. -2. The only way to mutate the state is by dispatching mutations on the store. +2. The only way to mutate the state is by committing **mutations**, which are synchronous transactions. -3. Mutations must be synchronous, and the only side effects they produce should be mutating the state. +3. Asynchronous logic should be encapsulated in, and can be composed with **actions**. -4. We can expose a more expressive state mutation API by defining actions. Actions can encapsulate asynchronous logic such as data fetching, and the only side effects they produce should be dispatching mutations. +As long as you follow these rules, it's up to you how to structure your project. If your store file gets too big, simply start splitting the actions, mutations and getters into separate files. -5. Components use getters to retrieve state from the store, and call actions to mutate the state. - -The nice thing about Vuex mutations, actions and getters is that **they are all just functions**. As long as you follow these rules, it's up to you how to structure your project. However, it's nice to have some conventions so that you can instantly become familiar with another project that uses Vuex, so here are some recommended structures depending on the scale of your app. - -### Simple Project - -For a simple project, we can simply define the **store** and the **actions** in respective files: - -``` bash -. -├── index.html -├── main.js -├── components -│ ├── App.vue -│ └── ... -└── vuex - ├── store.js # exports the store (with initial state and mutations) - └── actions.js # exports all actions -``` - -For an actual example, check out the [Counter example](https://github.com/vuejs/vuex/tree/master/examples/counter) or the [TodoMVC example](https://github.com/vuejs/vuex/tree/master/examples/todomvc). - -Alternatively, you can also split out mutations into its own file. - -### Medium to Large Project - -For any non-trivial app, we probably want to further split Vuex-related code into multiple "modules" (roughly comparable to "stores" in vanilla Flux, and "reducers" in Redux), each dealing with a specific domain of our app. Each module would be managing a sub-tree of the state, exporting the initial state for that sub-tree and all mutations that operate on that sub-tree: +For any non-trivial app, we will likely need to leverage modules. Here's an example project structure: ``` bash ├── index.html ├── main.js ├── api -│ └── ... # abstractions for making API requests +│   └── ... # abstractions for making API requests ├── components -│ ├── App.vue -│ └── ... -└── vuex - ├── actions.js # exports all actions - ├── store.js # where we assemble modules and export the store - ├── mutation-types.js # constants +│   ├── App.vue +│   └── ... +└── store + ├── index.js # where we assemble modules and export the store + ├── actions.js # root actions + ├── mutations.js # root mutations └── modules - ├── cart.js # state and mutations for cart - └── products.js # state and mutations for products -``` - -A typical module looks like this: - -``` js -// vuex/modules/products.js -import { - RECEIVE_PRODUCTS, - ADD_TO_CART -} from '../mutation-types' - -// initial state -const state = { - all: [] -} - -// mutations -const mutations = { - [RECEIVE_PRODUCTS] (state, products) { - state.all = products - }, - - [ADD_TO_CART] (state, productId) { - state.all.find(p => p.id === productId).inventory-- - } -} - -export default { - state, - mutations -} +    ├── cart.js # cart module +    └── products.js # products module ``` -And in `vuex/store.js`, we "assemble" multiple modules together to create the Vuex instance: - -``` js -// vuex/store.js -import Vue from 'vue' -import Vuex from '../../../src' -// import parts from modules -import cart from './modules/cart' -import products from './modules/products' - -Vue.use(Vuex) - -export default new Vuex.Store({ - // combine sub modules - modules: { - cart, - products - } -}) -``` - -Here, `cart` module's initial state will be attached to the root state tree as `store.state.cart`. In addition, **all the mutations defined in a sub-module only receive the sub-state-tree they are associated with**. So mutations defined in the `cart` module will receive `store.state.cart` as their first argument. - -The root of the sub-state-tree is irreplaceable inside the module itself. For example this won't work: - -``` js -const mutations = { - SOME_MUTATION (state) { - state = { ... } - } -} -``` - -Instead, always store actual state as a property of the sub-tree root: - -``` js -const mutations = { - SOME_MUTATION (state) { - state.value = { ... } - } -} -``` - -Since all modules simply export objects and functions, they are quite easy to test and maintain, and can be hot-reloaded. You are also free to alter the patterns used here to find a structure that fits your preference. - -Note that we do not put actions into modules, because a single action may dispatch mutations that affect multiple modules. It's also a good idea to decouple actions from the state shape and the implementation details of mutations for better separation of concerns. If the actions file gets too large, we can turn it into a folder and split out the implementations of long async actions into individual files. - -For an example, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/master/examples/shopping-cart). +As a reference, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart). From 446d71b5c1368159f5f7382bb21273e6f97762aa Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 13:31:53 +0900 Subject: [PATCH 33/80] Copy version 1.0 of docs/ja/structure.md --- docs/ja/structure.md | 133 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/docs/ja/structure.md b/docs/ja/structure.md index 2ba11e37b..b2547d8df 100644 --- a/docs/ja/structure.md +++ b/docs/ja/structure.md @@ -30,3 +30,136 @@ For any non-trivial app, we will likely need to leverage modules. Here's an exam ``` As a reference, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart). + +# アプリケーションの構造 + +Vuex は実際のところ、あなたがコードを構造化する方法を制限しません。それより高いレベルの原理原則を適用させます: + +1. アプリケーションの状態は単一のオブジェクトとして、ストアで保持されます +2. 状態を変更する唯一の方法は、ストアのミューテーションをディスパッチすることです +3. ミューテーションは同期的である必要があり、それらが発生させる副作用は、状態の変更にすべきです +4. アクションを定義することで、より表現的な状態変更のAPIを公開できます。アクションはデータ取得のような非同期ロジックを隠蔽することができ、それらが起こす副作用はミューテーションのディスパッチであるべきです +5. コンポーネントはゲッターを使って、ストアから状態を取り出し、状態を変更するアクションを呼び出します + +Vuex のミューテーション、アクション、ゲッターの良いところは、**それらが全てただの関数である**ということです。これらのルールに従っている限り、あなたのプロジェクトをどのように構造化するかはあなた次第です。しかし、 Vuex を利用した異なるプロジェクトにすぐに慣れることができるようにいくつかの規約があるとよいでしょう。なので、ここではアプリケーションの規模に応じて、いくつかの推奨の構造を紹介したいと思います。 + +### シンプルなプロジェクト + +シンプルなプロジェクトに対しては、単純に**ストア**と**アクション**をそれぞれのファイルに定義することができます: + +``` bash +. +├── index.html +├── main.js +├── components +│ ├── App.vue +│ └── ... +└── vuex + ├── store.js # ストア (初期状態とミューテーションを含む) を公開 + └── actions.js # 全てのアクションを公開 +``` + +実際の例として、 [Counter example](https://github.com/vuejs/vuex/tree/master/examples/counter) や [TodoMVC example](https://github.com/vuejs/vuex/tree/master/examples/todomvc) を確認してみてください。 + +あるいは、ミューテーションだけをそれ自身のファイルに分割することができます。 + +### 中規模から大規模なプロジェクト + +それなりに手の込んだアプリケーションであれば、 Vuex 関連のコードをそれぞれがアプリの特定のドメインを担う複数の"モジュール"に分割したくなります(ざっくりと比べれば、普通の Flux の"stores"、 Redux の"reducers"に相当します)。各モジュールは、状態のサブツリーを管理し、サブツリーの初期状態とサブツリーで動作する全ての変更を公開します。 + +```bash +├── index.html +├── main.js +├── api +│ └── ... # APIリクエストを作成するための抽象化 +├── components +│ ├── App.vue +│ └── ... +└── vuex + ├── actions.js # 全てのアクションのエクスポート + ├── store.js # モジュールを集め、ストアを公開する + ├── mutation-types.js # 定数 + └── modules + ├── cart.js # カートのための状態とミューテーション + └── products.js # 商品のための状態とミューテーション +``` + +典型的なモジュールは次のようになります: + +``` js +// vuex/modules/products.js +import { + RECEIVE_PRODUCTS, + ADD_TO_CART +} from '../mutation-types' + +// 初期状態 +const state = { + all: [] +} + +// ミューテーション +const mutations = { + [RECEIVE_PRODUCTS] (state, products) { + state.all = products + }, + + [ADD_TO_CART] (state, productId) { + state.all.find(p => p.id === productId).inventory-- + } +} + +export default { + state, + mutations +} +``` + +そして、`vuex/store.js` では、 Vuex インスタンスを生成するために複数のモジュールをひとつに集めて整理します( "assemble" ): + +```js +// vuex/store.js +import Vue from 'vue' +import Vuex from '../../../src' +// それぞれのモジュールをインポートする +import cart from './modules/cart' +import products from './modules/products' + +Vue.use(Vuex) + +export default new Vuex.Store({ + // サブモジュールを組み合わせる + modules: { + cart, + products + } +}) +``` + +ここで、 `cart` モジュールの初期状態は、ルートステートツリーの `store.state.cart` としてアタッチされます。加えて、全てのサブモジュールで定義されたミューテーションは、それに関係したサブステートツリーを受け取ります。なので、`cart` モジュールで定義されたミューテーションは、`store.state.cart` を第一引数として受け取ることになります。 + +サブステートツリーのルートは、モジュール内部で自身を取りかえることはできません。例えば、次のコードは動作しません: + +``` js +const mutations = { + SOME_MUTATION (state) { + state = { ... } + } +} +``` + +代わりに、常にサブツリーのルートのプロパティに実際の状態を保存します。 + +``` js +const mutations = { + SOME_MUTATION (state) { + state.value = { ... } + } +} +``` + +全てのモジュールは単純にオブジェクトと関数をエクスポートしているので、それらはかなり簡単にテストでき、メンテナンスも容易です。そして、ホットリロードできます。また、あなたの好みに合った構造を見つけるためにここで紹介したパターンを変えることは自由です。 + +アクションをモジュール内に置くことはできないことに注意しましょう。なぜならば、ひとつのアクションが複数のモジュールに影響を与えるミューテーションをディスパッチするかもしれないからです。また、より「関心の分離」を進めるため、状態の形やミューテーションの実装の詳細からアクションを分けることをおすすめします。もし、アクションのファイルが非常に大きくなったら、それをフォルダに移し、長い非同期アクションの実装をいくつかのファイルに分割できます。 + +例として、 [Shopping Cart Example](https://github.com/vuejs/vuex/tree/master/examples/shopping-cart) をみてみるとよいでしょう。 From 073e7fb97026a24491bdf8f7ada800a50e47b677 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 14:19:05 +0900 Subject: [PATCH 34/80] Translate structure.md --- docs/ja/structure.md | 161 ++++--------------------------------------- 1 file changed, 14 insertions(+), 147 deletions(-) diff --git a/docs/ja/structure.md b/docs/ja/structure.md index b2547d8df..5ff3cd7a6 100644 --- a/docs/ja/structure.md +++ b/docs/ja/structure.md @@ -1,165 +1,32 @@ -# Application Structure +# アプリケーションの構造 -Vuex doesn't really restrict how you structure your code. Rather, it enforces a set of high-level principles: +Vuex は実際のところ、あなたがコードを構造化する方法を制限しません。もっと正確に言うと、それより高いレベルの原理原則を適用させます: -1. Application-level state is centralized in the store. +1. アプリケーションレベルの状態はストアに集約されます。 -2. The only way to mutate the state is by committing **mutations**, which are synchronous transactions. +2. 状態を変更する唯一の方法は、同期的に処理を行う **ミューテーション** をコミットすることのみです。 -3. Asynchronous logic should be encapsulated in, and can be composed with **actions**. +3. 非同期的なロジックはカプセル化されるべきであり、それは **アクション** によって構成されます。 -As long as you follow these rules, it's up to you how to structure your project. If your store file gets too big, simply start splitting the actions, mutations and getters into separate files. +これらのルールに従っている限り、プロジェクトをどのように構造化するかはあなた次第です。もしストアファイルが大きくなり過ぎたら、単純にアクションやミューテーション、ゲッターをそれぞれ別のファイルに切り出すことができます。 -For any non-trivial app, we will likely need to leverage modules. Here's an example project structure: +それなりに手の込んだアプリケーションであれば、モジュールを活用する必要が出てきそうです。プロジェクトの構造の例は以下のようになります: ``` bash ├── index.html ├── main.js ├── api -│   └── ... # abstractions for making API requests +│   └── ... # API へのリクエスト作成 ├── components │   ├── App.vue │   └── ... └── store - ├── index.js # where we assemble modules and export the store - ├── actions.js # root actions - ├── mutations.js # root mutations + ├── index.js # モジュールを集めてストアをエクスポートする + ├── actions.js # アクションのルートファイル + ├── mutations.js # ミューテーションのルートファイル └── modules -    ├── cart.js # cart module -    └── products.js # products module -``` - -As a reference, check out the [Shopping Cart Example](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart). - -# アプリケーションの構造 - -Vuex は実際のところ、あなたがコードを構造化する方法を制限しません。それより高いレベルの原理原則を適用させます: - -1. アプリケーションの状態は単一のオブジェクトとして、ストアで保持されます -2. 状態を変更する唯一の方法は、ストアのミューテーションをディスパッチすることです -3. ミューテーションは同期的である必要があり、それらが発生させる副作用は、状態の変更にすべきです -4. アクションを定義することで、より表現的な状態変更のAPIを公開できます。アクションはデータ取得のような非同期ロジックを隠蔽することができ、それらが起こす副作用はミューテーションのディスパッチであるべきです -5. コンポーネントはゲッターを使って、ストアから状態を取り出し、状態を変更するアクションを呼び出します - -Vuex のミューテーション、アクション、ゲッターの良いところは、**それらが全てただの関数である**ということです。これらのルールに従っている限り、あなたのプロジェクトをどのように構造化するかはあなた次第です。しかし、 Vuex を利用した異なるプロジェクトにすぐに慣れることができるようにいくつかの規約があるとよいでしょう。なので、ここではアプリケーションの規模に応じて、いくつかの推奨の構造を紹介したいと思います。 - -### シンプルなプロジェクト - -シンプルなプロジェクトに対しては、単純に**ストア**と**アクション**をそれぞれのファイルに定義することができます: - -``` bash -. -├── index.html -├── main.js -├── components -│ ├── App.vue -│ └── ... -└── vuex - ├── store.js # ストア (初期状態とミューテーションを含む) を公開 - └── actions.js # 全てのアクションを公開 +    ├── cart.js # cart モジュール +    └── products.js # products モジュール ``` -実際の例として、 [Counter example](https://github.com/vuejs/vuex/tree/master/examples/counter) や [TodoMVC example](https://github.com/vuejs/vuex/tree/master/examples/todomvc) を確認してみてください。 - -あるいは、ミューテーションだけをそれ自身のファイルに分割することができます。 - -### 中規模から大規模なプロジェクト - -それなりに手の込んだアプリケーションであれば、 Vuex 関連のコードをそれぞれがアプリの特定のドメインを担う複数の"モジュール"に分割したくなります(ざっくりと比べれば、普通の Flux の"stores"、 Redux の"reducers"に相当します)。各モジュールは、状態のサブツリーを管理し、サブツリーの初期状態とサブツリーで動作する全ての変更を公開します。 - -```bash -├── index.html -├── main.js -├── api -│ └── ... # APIリクエストを作成するための抽象化 -├── components -│ ├── App.vue -│ └── ... -└── vuex - ├── actions.js # 全てのアクションのエクスポート - ├── store.js # モジュールを集め、ストアを公開する - ├── mutation-types.js # 定数 - └── modules - ├── cart.js # カートのための状態とミューテーション - └── products.js # 商品のための状態とミューテーション -``` - -典型的なモジュールは次のようになります: - -``` js -// vuex/modules/products.js -import { - RECEIVE_PRODUCTS, - ADD_TO_CART -} from '../mutation-types' - -// 初期状態 -const state = { - all: [] -} - -// ミューテーション -const mutations = { - [RECEIVE_PRODUCTS] (state, products) { - state.all = products - }, - - [ADD_TO_CART] (state, productId) { - state.all.find(p => p.id === productId).inventory-- - } -} - -export default { - state, - mutations -} -``` - -そして、`vuex/store.js` では、 Vuex インスタンスを生成するために複数のモジュールをひとつに集めて整理します( "assemble" ): - -```js -// vuex/store.js -import Vue from 'vue' -import Vuex from '../../../src' -// それぞれのモジュールをインポートする -import cart from './modules/cart' -import products from './modules/products' - -Vue.use(Vuex) - -export default new Vuex.Store({ - // サブモジュールを組み合わせる - modules: { - cart, - products - } -}) -``` - -ここで、 `cart` モジュールの初期状態は、ルートステートツリーの `store.state.cart` としてアタッチされます。加えて、全てのサブモジュールで定義されたミューテーションは、それに関係したサブステートツリーを受け取ります。なので、`cart` モジュールで定義されたミューテーションは、`store.state.cart` を第一引数として受け取ることになります。 - -サブステートツリーのルートは、モジュール内部で自身を取りかえることはできません。例えば、次のコードは動作しません: - -``` js -const mutations = { - SOME_MUTATION (state) { - state = { ... } - } -} -``` - -代わりに、常にサブツリーのルートのプロパティに実際の状態を保存します。 - -``` js -const mutations = { - SOME_MUTATION (state) { - state.value = { ... } - } -} -``` - -全てのモジュールは単純にオブジェクトと関数をエクスポートしているので、それらはかなり簡単にテストでき、メンテナンスも容易です。そして、ホットリロードできます。また、あなたの好みに合った構造を見つけるためにここで紹介したパターンを変えることは自由です。 - -アクションをモジュール内に置くことはできないことに注意しましょう。なぜならば、ひとつのアクションが複数のモジュールに影響を与えるミューテーションをディスパッチするかもしれないからです。また、より「関心の分離」を進めるため、状態の形やミューテーションの実装の詳細からアクションを分けることをおすすめします。もし、アクションのファイルが非常に大きくなったら、それをフォルダに移し、長い非同期アクションの実装をいくつかのファイルに分割できます。 - -例として、 [Shopping Cart Example](https://github.com/vuejs/vuex/tree/master/examples/shopping-cart) をみてみるとよいでしょう。 +参考として [Shopping Cart Example](https://github.com/vuejs/vuex/tree/dev/examples/shopping-cart) をみてみるのもよいでしょう。 From 37bdc43097819b8cd27c4215042a5d9a14b8460e Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:06:02 +0900 Subject: [PATCH 35/80] Copy docs/en/testing.md of 1.0 to docs/ja/testing.md --- docs/ja/testing.md | 167 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 docs/ja/testing.md diff --git a/docs/ja/testing.md b/docs/ja/testing.md new file mode 100644 index 000000000..b4aae115d --- /dev/null +++ b/docs/ja/testing.md @@ -0,0 +1,167 @@ +# Testing + +The main parts we want to unit test in Vuex are mutations and actions. + +### Testing Mutations + +Mutations are very straightforward to test, because they are just functions that completely rely on their arguments. One trick is that if you are using ES2015 modules and put your mutations inside your `store.js` file, in addition to the default export, you can also export the mutations as a named export: + +``` js +const state = { ... } + +// export mutations as a named export +export const mutations = { ... } + +export default new Vuex.Store({ + state, + mutations +}) +``` + +Example testing a mutation using Mocha + Chai (you can use any framework/assertion libraries you like): + +``` js +// mutations.js +export const INCREMENT = state => state.count++ +``` + +``` js +// mutations.spec.js +import { expect } from 'chai' +import { mutations } from './store' + +// destructure assign mutations +const { INCREMENT } = mutations + +describe('mutations', () => { + it('INCREMENT', () => { + // mock state + const state = { count: 0 } + // apply mutation + INCREMENT(state) + // assert result + expect(state.count).to.equal(1) + }) +}) +``` + +### Testing Actions + +Actions can be a bit more tricky because they may call out to external APIs. When testing actions, we usually need to do some level of mocking - for example, we can abstract the API calls into a service and mock that service inside our tests. In order to easily mock dependencies, we can use Webpack and [inject-loader](https://github.com/plasticine/inject-loader) to bundle our test files. + +Example testing an async action: + +``` js +// actions.js +import shop from '../api/shop' + +export const getAllProducts = ({ dispatch }) => { + dispatch('REQUEST_PRODUCTS') + shop.getProducts(products => { + dispatch('RECEIVE_PRODUCTS', products) + }) +} +``` + +``` js +// actions.spec.js + +// use require syntax for inline loaders. +// with inject-loader, this returns a module factory +// that allows us to inject mocked dependencies. +import { expect } from 'chai' +const actionsInjector = require('inject!./actions') + +// create the module with our mocks +const actions = actionsInjector({ + '../api/shop': { + getProducts (cb) { + setTimeout(() => { + cb([ /* mocked response */ ]) + }, 100) + } + } +}) + +// helper for testing action with expected mutations +const testAction = (action, args, state, expectedMutations, done) => { + let count = 0 + // mock dispatch + const dispatch = (name, ...payload) => { + const mutation = expectedMutations[count] + expect(mutation.name).to.equal(name) + if (payload) { + expect(mutation.payload).to.deep.equal(payload) + } + count++ + if (count >= expectedMutations.length) { + done() + } + } + // call the action with mocked store and arguments + action({dispatch, state}, ...args) + + // check if no mutations should have been dispatched + if (expectedMutations.length === 0) { + expect(count).to.equal(0) + done() + } +} + +describe('actions', () => { + it('getAllProducts', done => { + testAction(actions.getAllProducts, [], {}, [ + { name: 'REQUEST_PRODUCTS' }, + { name: 'RECEIVE_PRODUCTS', payload: [ /* mocked response */ ] } + ], done) + }) +}) +``` + +### Running Tests + +If your mutations and actions are written properly, the tests should have no direct dependency on Browser APIs after proper mocking. Thus you can simply bundle the tests with Webpack and run it directly in Node. Alternatively, you can use `mocha-loader` or Karma + `karma-webpack` to run the tests in real browsers. + +#### Running in Node + +Create the following webpack config: + +``` js +module.exports = { + entry: './test.js', + output: { + path: __dirname, + filename: 'test-bundle.js' + }, + module: { + loaders: [ + { + test: /\.js$/, + loader: 'babel', + exclude: /node_modules/ + } + ] + }, + babel: { + presets: ['es2015'] + } +} +``` + +Then: + +``` bash +webpack +mocha test-bundle.js +``` + +#### Running in Browser + +1. Install `mocha-loader` +2. Change the `entry` from the Webpack config above to `'mocha!babel!./test.js'`. +3. Start `webpack-dev-server` using the config +4. Go to `localhost:8080/webpack-dev-server/test-bundle`. + +#### Running in Browser with Karma + karma-webpack + +Consult the setup in [vue-loader documentation](http://vue-loader.vuejs.org/en/workflow/testing.html). From 153225d413ccb4b4f2adbadd4314520da9854899 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:06:34 +0900 Subject: [PATCH 36/80] Copy docs/en/testing.md of 2.0, understanding diff 1.0 and 2.0 --- docs/ja/testing.md | 79 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/docs/ja/testing.md b/docs/ja/testing.md index b4aae115d..55098ca18 100644 --- a/docs/ja/testing.md +++ b/docs/ja/testing.md @@ -22,7 +22,9 @@ Example testing a mutation using Mocha + Chai (you can use any framework/asserti ``` js // mutations.js -export const INCREMENT = state => state.count++ +export const mutations = { + increment: state => state.count++ +} ``` ``` js @@ -31,14 +33,14 @@ import { expect } from 'chai' import { mutations } from './store' // destructure assign mutations -const { INCREMENT } = mutations +const { increment } = mutations describe('mutations', () => { it('INCREMENT', () => { // mock state const state = { count: 0 } // apply mutation - INCREMENT(state) + increment(state) // assert result expect(state.count).to.equal(1) }) @@ -84,12 +86,13 @@ const actions = actionsInjector({ }) // helper for testing action with expected mutations -const testAction = (action, args, state, expectedMutations, done) => { +const testAction = (action, payload, state, expectedMutations, done) => { let count = 0 - // mock dispatch - const dispatch = (name, ...payload) => { + + // mock commit + const commit = (type, payload) => { const mutation = expectedMutations[count] - expect(mutation.name).to.equal(name) + expect(mutation.type).to.equal(type) if (payload) { expect(mutation.payload).to.deep.equal(payload) } @@ -98,8 +101,9 @@ const testAction = (action, args, state, expectedMutations, done) => { done() } } + // call the action with mocked store and arguments - action({dispatch, state}, ...args) + action({ commit, state }, payload) // check if no mutations should have been dispatched if (expectedMutations.length === 0) { @@ -110,23 +114,71 @@ const testAction = (action, args, state, expectedMutations, done) => { describe('actions', () => { it('getAllProducts', done => { - testAction(actions.getAllProducts, [], {}, [ - { name: 'REQUEST_PRODUCTS' }, - { name: 'RECEIVE_PRODUCTS', payload: [ /* mocked response */ ] } + testAction(actions.getAllProducts, null, {}, [ + { type: 'REQUEST_PRODUCTS' }, + { type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } } ], done) }) }) ``` +### Testing Getters + +If your getters have complicated computation, it is worth testing them. Getters are also very straightforward to test as same reason as mutations. + +Example testing a getter: + +``` js +// getters.js +export const getters = { + filteredProducts (state, { filterCategory }) { + return state.products.filter(product => { + return product.category === filterCategory + }) + } +} +``` + +``` js +// getters.spec.js +import { expect } from 'chai' +import { getters } from './getters' + +describe('getters', () => { + it('filteredProducts', () => { + // mock state + const state = { + products: [ + { id: 1, title: 'Apple', category: 'fruit' }, + { id: 2, title: 'Orange', category: 'fruit' }, + { id: 3, title: 'Carrot', category: 'vegetable' } + ] + } + // mock getter + const filterCategory = 'fruit' + + // get the result from the getter + const result = getters.filteredProducts(state, { filterCategory }) + + // assert the result + expect(result).to.deep.equal([ + { id: 1, title: 'Apple', category: 'fruit' }, + { id: 2, title: 'Orange', category: 'fruit' } + ]) + }) +}) +``` + ### Running Tests If your mutations and actions are written properly, the tests should have no direct dependency on Browser APIs after proper mocking. Thus you can simply bundle the tests with Webpack and run it directly in Node. Alternatively, you can use `mocha-loader` or Karma + `karma-webpack` to run the tests in real browsers. #### Running in Node -Create the following webpack config: +Create the following webpack config (together with proper [`.babelrc`](https://babeljs.io/docs/usage/babelrc/)): ``` js +// webpack.config.js module.exports = { entry: './test.js', output: { @@ -141,9 +193,6 @@ module.exports = { exclude: /node_modules/ } ] - }, - babel: { - presets: ['es2015'] } } ``` From f6376ea713db01e39aac17513095903dd58623ae Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:07:55 +0900 Subject: [PATCH 37/80] Copy version 1.0 of docs/ja/testing.md --- docs/ja/testing.md | 168 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/docs/ja/testing.md b/docs/ja/testing.md index 55098ca18..d09d76e62 100644 --- a/docs/ja/testing.md +++ b/docs/ja/testing.md @@ -214,3 +214,171 @@ mocha test-bundle.js #### Running in Browser with Karma + karma-webpack Consult the setup in [vue-loader documentation](http://vue-loader.vuejs.org/en/workflow/testing.html). + +# テスト + +私達が Vuex でユニットテストしたい主な部分はミューテーションとアクションです。 + +### ミューテーションのテスト + +ミューテーションは完全に引数に依存しているだけの関数であるため、テストするのがとても簡単です。効果的なやり方として、もし ES2015 のモジュールを使っていて、 `store.js` ファイルの中にミューテーションがあるなら、デフォルトエクスポートに加えて、名前付きエクスポートでミューテーションをエクスポートできます。 + +``` js +const state = { ... } + +// 名前付きエクスポートでミューテーションをエクスポートする +export const mutations = { ... } + +export default new Vuex.Store({ + state, + mutations +}) +``` + +Mocha + Chai を使用してミューテーションをテストする例です (あなたの好きな任意のフレームワーク/アサーションライブラリを使用できます): + +``` js +// mutations.js +export const INCREMENT = state => state.count++ +``` + +``` js +// mutations.spec.js +import { expect } from 'chai' +import { mutations } from './store' + +// ミューテーションの分割束縛 +const { INCREMENT } = mutations + +describe('mutations', () => { + it('INCREMENT', () => { + // ステートのモック + const state = { count: 0 } + // ミューテーションを適用 + INCREMENT(state) + // 結果を検証 + expect(state.count).to.equal(1) + }) +}) +``` + +### アクションのテスト + +アクションは外部の API を呼び出す可能性があるためより少し注意が必要です。アクションをテストするとき、通常、いくつかの段階でモックを作る必要があります。例えば、API 呼び出しをサービスとして抽象化し、そしてテストの内部ではそのサービスをモックにすることができます。簡単に依存をモック化するために、Webpack と [inject-loader](https://github.com/plasticine/inject-loader) をテストファイルにバンドルして使用することができます。 + +非同期アクションのテストの例: + +``` js +// actions.js +import shop from '../api/shop' + +export const getAllProducts = ({ dispatch }) => { + dispatch('REQUEST_PRODUCTS') + shop.getProducts(products => { + dispatch('RECEIVE_PRODUCTS', products) + }) +} +``` + +``` js +// actions.spec.js + +// inline loader のために require 構文を使用する +// inject-loader は、モック化された依存関係を注入できるようにする +// モジュールファクトリを返す +import { expect } from 'chai' +const actionsInjector = require('inject!./actions') + +// モックによってモジュールを作成する +const actions = actionsInjector({ + '../api/shop': { + getProducts (cb) { + setTimeout(() => { + cb([ /* レスポンスのモック */ ]) + }, 100) + } + } +}) + +// アクションが期待されるミューテーションを呼び出すかをテストするためのヘルパー +const testAction = (action, args, state, expectedMutations, done) => { + let count = 0 + // ディスパッチのモック + const dispatch = (name, ...payload) => { + const mutation = expectedMutations[count] + expect(mutation.name).to.equal(name) + if (payload) { + expect(mutation.payload).to.deep.equal(payload) + } + count++ + if (count >= expectedMutations.length) { + done() + } + } + // モック化したストアと引数でアクションを呼び出す + action({dispatch, state}, ...args) + + // 呼び出されるべきミューテーションが残っていないことを確認する + if (expectedMutations.length === 0) { + expect(count).to.equal(0) + done() + } +} + +describe('actions', () => { + it('getAllProducts', done => { + testAction(actions.getAllProducts, [], {}, [ + { name: 'REQUEST_PRODUCTS' }, + { name: 'RECEIVE_PRODUCTS', payload: [ /* レスポンスのモック */ ] } + ], done) + }) +}) +``` + +### テストの実行 + +ミューテーションやアクションが適切に書かれている場合は、適切にモック化された後、テストコードはブラウザの API に直接依存関係を持つことはないでしょう。したがって、単純に Webpack でテストをバンドルでき、それを直接 Node で実行できます。別の方法として、本当のブラウザでテストを実行するためには、`mocha-loader` または Karma + `karma-webpack` を使用できます。 + +#### Node での実行 + +以下のような webpack の設定を作成します: + +``` js +module.exports = { + entry: './test.js', + output: { + path: __dirname, + filename: 'test-bundle.js' + }, + module: { + loaders: [ + { + test: /\.js$/, + loader: 'babel', + exclude: /node_modules/ + } + ] + }, + babel: { + presets: ['es2015'] + } +} +``` + +その後、下記コマンドを実行します: + +``` bash +webpack +mocha test-bundle.js +``` + +#### ブラウザでの実行 + +1. `mocha-loader` をインストール +2. 上記 Webpack 設定から `entry` を `'mocha!babel!./test.js'` に変更 +3. 設定を使用して `webpack-dev-server` を開始 +4. `localhost:8080/webpack-dev-server/test-bundle` に移動 + +#### Karma + karma-webpack を使ったブラウザでの実行 + +[vue-loader documentation](http://vuejs.github.io/vue-loader/en/workflow/testing.html) 内のセットアップ方法を参考にしてください。 From 1a47bb728385b8bb94cabc527faea028a80bf02c Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:19:49 +0900 Subject: [PATCH 38/80] Translate testing mutations --- docs/ja/testing.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ja/testing.md b/docs/ja/testing.md index d09d76e62..4d37d6e76 100644 --- a/docs/ja/testing.md +++ b/docs/ja/testing.md @@ -1,15 +1,15 @@ -# Testing +# テスト -The main parts we want to unit test in Vuex are mutations and actions. +私たちが Vuex でユニットテストしたい主な部分はミューテーションとアクションです。 -### Testing Mutations +### ミューテーションのテスト -Mutations are very straightforward to test, because they are just functions that completely rely on their arguments. One trick is that if you are using ES2015 modules and put your mutations inside your `store.js` file, in addition to the default export, you can also export the mutations as a named export: +ミューテーションは完全に引数に依存しているだけの関数であるため、テストするのがとても簡単です。効果的なやり方として、もし ES2015 のモジュールを使っていて `store.js` ファイルの中にミューテーションがあるなら、デフォルトエクスポートに加えて、名前付きエクスポートでミューテーションをエクスポートできます。 ``` js const state = { ... } -// export mutations as a named export +// 名前付きエクスポートでミューテーションをエクスポートする export const mutations = { ... } export default new Vuex.Store({ @@ -18,7 +18,7 @@ export default new Vuex.Store({ }) ``` -Example testing a mutation using Mocha + Chai (you can use any framework/assertion libraries you like): +Mocha + Chai を使用してミューテーションをテストする例です(あなたの好きな任意のフレームワーク/アサーションライブラリを使用できます): ``` js // mutations.js @@ -32,16 +32,16 @@ export const mutations = { import { expect } from 'chai' import { mutations } from './store' -// destructure assign mutations +// ミューテーションの分割束縛 const { increment } = mutations describe('mutations', () => { it('INCREMENT', () => { - // mock state + // ステートのモック const state = { count: 0 } - // apply mutation + // ミューテーションを適用する increment(state) - // assert result + // 結果を検証する expect(state.count).to.equal(1) }) }) From 6924e0a00bf8333fdb9d5b9150ece35c3d324f67 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:37:08 +0900 Subject: [PATCH 39/80] Translate testing actions --- docs/ja/structure.md | 2 +- docs/ja/testing.md | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/ja/structure.md b/docs/ja/structure.md index 5ff3cd7a6..9c7d4f608 100644 --- a/docs/ja/structure.md +++ b/docs/ja/structure.md @@ -16,7 +16,7 @@ Vuex は実際のところ、あなたがコードを構造化する方法を制 ├── index.html ├── main.js ├── api -│   └── ... # API へのリクエスト作成 +│   └── ... # API 呼び出しを抽象化する ├── components │   ├── App.vue │   └── ... diff --git a/docs/ja/testing.md b/docs/ja/testing.md index 4d37d6e76..6e5c150a7 100644 --- a/docs/ja/testing.md +++ b/docs/ja/testing.md @@ -47,11 +47,11 @@ describe('mutations', () => { }) ``` -### Testing Actions - -Actions can be a bit more tricky because they may call out to external APIs. When testing actions, we usually need to do some level of mocking - for example, we can abstract the API calls into a service and mock that service inside our tests. In order to easily mock dependencies, we can use Webpack and [inject-loader](https://github.com/plasticine/inject-loader) to bundle our test files. +### アクションのテスト + +アクションは外部の API を呼び出す可能性があるため、ミューテーションのテストよりも少し注意が必要です。アクションをテストするとき、通常、いくつかの段階でモックを作る必要があります。例えば API 呼び出しをサービスとして抽象化し、そしてテストの内部ではそのサービスをモックにすることができます。簡単に依存関係をモック化するために、Webpack と [inject-loader](https://github.com/plasticine/inject-loader) をテストファイルにバンドルして使用することができます。 -Example testing an async action: +非同期なアクションのテストの例: ``` js // actions.js @@ -68,28 +68,27 @@ export const getAllProducts = ({ dispatch }) => { ``` js // actions.spec.js -// use require syntax for inline loaders. -// with inject-loader, this returns a module factory -// that allows us to inject mocked dependencies. +// inline loader のために require 構文を使用する +// ここでは inject-loader を使って、モック化された依存関係を注入できるようにするモジュールファクトリーを返す import { expect } from 'chai' const actionsInjector = require('inject!./actions') -// create the module with our mocks +// モックによってモジュールを作成する const actions = actionsInjector({ '../api/shop': { getProducts (cb) { setTimeout(() => { - cb([ /* mocked response */ ]) + cb([ /* レスポンスのモック */ ]) }, 100) } } }) -// helper for testing action with expected mutations +// 期待されるミューテーションをアクションが呼び出すかをテストするためのヘルパー const testAction = (action, payload, state, expectedMutations, done) => { let count = 0 - // mock commit + // コミットをモックする const commit = (type, payload) => { const mutation = expectedMutations[count] expect(mutation.type).to.equal(type) @@ -102,10 +101,10 @@ const testAction = (action, payload, state, expectedMutations, done) => { } } - // call the action with mocked store and arguments + // モック化したストアと引数でアクションを呼び出す action({ commit, state }, payload) - // check if no mutations should have been dispatched + // 呼び出されるべきミューテーションが残っていないか確認する if (expectedMutations.length === 0) { expect(count).to.equal(0) done() @@ -116,7 +115,7 @@ describe('actions', () => { it('getAllProducts', done => { testAction(actions.getAllProducts, null, {}, [ { type: 'REQUEST_PRODUCTS' }, - { type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } } + { type: 'RECEIVE_PRODUCTS', payload: { /* レスポンスのモック */ } } ], done) }) }) From fd0eaa4e8561dd0ef8cfdbd80ee266dfb4397f48 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:51:20 +0900 Subject: [PATCH 40/80] Translate docs/ja/testing.md --- docs/ja/testing.md | 200 ++++----------------------------------------- 1 file changed, 16 insertions(+), 184 deletions(-) diff --git a/docs/ja/testing.md b/docs/ja/testing.md index 6e5c150a7..f077527e1 100644 --- a/docs/ja/testing.md +++ b/docs/ja/testing.md @@ -121,11 +121,11 @@ describe('actions', () => { }) ``` -### Testing Getters +### ゲッターのテスト -If your getters have complicated computation, it is worth testing them. Getters are also very straightforward to test as same reason as mutations. +もしゲッターが複雑な計算を行っているならば、テストコードを書く価値があります。ゲッターはミューテーションと同様の理由でテストしやすいです。 -Example testing a getter: +ゲッターのテストの例: ``` js // getters.js @@ -145,7 +145,7 @@ import { getters } from './getters' describe('getters', () => { it('filteredProducts', () => { - // mock state + // ステートをモックする const state = { products: [ { id: 1, title: 'Apple', category: 'fruit' }, @@ -153,13 +153,13 @@ describe('getters', () => { { id: 3, title: 'Carrot', category: 'vegetable' } ] } - // mock getter + // ゲッターをモックする const filterCategory = 'fruit' - // get the result from the getter + // ゲッターから結果を受け取る const result = getters.filteredProducts(state, { filterCategory }) - // assert the result + // 結果を検証する expect(result).to.deep.equal([ { id: 1, title: 'Apple', category: 'fruit' }, { id: 2, title: 'Orange', category: 'fruit' } @@ -168,181 +168,16 @@ describe('getters', () => { }) ``` -### Running Tests - -If your mutations and actions are written properly, the tests should have no direct dependency on Browser APIs after proper mocking. Thus you can simply bundle the tests with Webpack and run it directly in Node. Alternatively, you can use `mocha-loader` or Karma + `karma-webpack` to run the tests in real browsers. - -#### Running in Node - -Create the following webpack config (together with proper [`.babelrc`](https://babeljs.io/docs/usage/babelrc/)): - -``` js -// webpack.config.js -module.exports = { - entry: './test.js', - output: { - path: __dirname, - filename: 'test-bundle.js' - }, - module: { - loaders: [ - { - test: /\.js$/, - loader: 'babel', - exclude: /node_modules/ - } - ] - } -} -``` - -Then: - -``` bash -webpack -mocha test-bundle.js -``` - -#### Running in Browser - -1. Install `mocha-loader` -2. Change the `entry` from the Webpack config above to `'mocha!babel!./test.js'`. -3. Start `webpack-dev-server` using the config -4. Go to `localhost:8080/webpack-dev-server/test-bundle`. - -#### Running in Browser with Karma + karma-webpack - -Consult the setup in [vue-loader documentation](http://vue-loader.vuejs.org/en/workflow/testing.html). - -# テスト - -私達が Vuex でユニットテストしたい主な部分はミューテーションとアクションです。 - -### ミューテーションのテスト - -ミューテーションは完全に引数に依存しているだけの関数であるため、テストするのがとても簡単です。効果的なやり方として、もし ES2015 のモジュールを使っていて、 `store.js` ファイルの中にミューテーションがあるなら、デフォルトエクスポートに加えて、名前付きエクスポートでミューテーションをエクスポートできます。 - -``` js -const state = { ... } - -// 名前付きエクスポートでミューテーションをエクスポートする -export const mutations = { ... } - -export default new Vuex.Store({ - state, - mutations -}) -``` - -Mocha + Chai を使用してミューテーションをテストする例です (あなたの好きな任意のフレームワーク/アサーションライブラリを使用できます): - -``` js -// mutations.js -export const INCREMENT = state => state.count++ -``` - -``` js -// mutations.spec.js -import { expect } from 'chai' -import { mutations } from './store' - -// ミューテーションの分割束縛 -const { INCREMENT } = mutations - -describe('mutations', () => { - it('INCREMENT', () => { - // ステートのモック - const state = { count: 0 } - // ミューテーションを適用 - INCREMENT(state) - // 結果を検証 - expect(state.count).to.equal(1) - }) -}) -``` - -### アクションのテスト - -アクションは外部の API を呼び出す可能性があるためより少し注意が必要です。アクションをテストするとき、通常、いくつかの段階でモックを作る必要があります。例えば、API 呼び出しをサービスとして抽象化し、そしてテストの内部ではそのサービスをモックにすることができます。簡単に依存をモック化するために、Webpack と [inject-loader](https://github.com/plasticine/inject-loader) をテストファイルにバンドルして使用することができます。 - -非同期アクションのテストの例: - -``` js -// actions.js -import shop from '../api/shop' - -export const getAllProducts = ({ dispatch }) => { - dispatch('REQUEST_PRODUCTS') - shop.getProducts(products => { - dispatch('RECEIVE_PRODUCTS', products) - }) -} -``` - -``` js -// actions.spec.js - -// inline loader のために require 構文を使用する -// inject-loader は、モック化された依存関係を注入できるようにする -// モジュールファクトリを返す -import { expect } from 'chai' -const actionsInjector = require('inject!./actions') - -// モックによってモジュールを作成する -const actions = actionsInjector({ - '../api/shop': { - getProducts (cb) { - setTimeout(() => { - cb([ /* レスポンスのモック */ ]) - }, 100) - } - } -}) - -// アクションが期待されるミューテーションを呼び出すかをテストするためのヘルパー -const testAction = (action, args, state, expectedMutations, done) => { - let count = 0 - // ディスパッチのモック - const dispatch = (name, ...payload) => { - const mutation = expectedMutations[count] - expect(mutation.name).to.equal(name) - if (payload) { - expect(mutation.payload).to.deep.equal(payload) - } - count++ - if (count >= expectedMutations.length) { - done() - } - } - // モック化したストアと引数でアクションを呼び出す - action({dispatch, state}, ...args) - - // 呼び出されるべきミューテーションが残っていないことを確認する - if (expectedMutations.length === 0) { - expect(count).to.equal(0) - done() - } -} - -describe('actions', () => { - it('getAllProducts', done => { - testAction(actions.getAllProducts, [], {}, [ - { name: 'REQUEST_PRODUCTS' }, - { name: 'RECEIVE_PRODUCTS', payload: [ /* レスポンスのモック */ ] } - ], done) - }) -}) -``` - ### テストの実行 -ミューテーションやアクションが適切に書かれている場合は、適切にモック化された後、テストコードはブラウザの API に直接依存関係を持つことはないでしょう。したがって、単純に Webpack でテストをバンドルでき、それを直接 Node で実行できます。別の方法として、本当のブラウザでテストを実行するためには、`mocha-loader` または Karma + `karma-webpack` を使用できます。 +ミューテーションやアクションが適切に書かれている場合は、適切にモック化された後、テストコードはブラウザの API に直接依存関係を持つことはないでしょう。したがって、単純に Webpack でテストをバンドルでき、それを直接 Node で実行できます。別の方法として、本当のブラウザでテストを実行するためには `mocha-loader` または Karma + `karma-webpack` を使用できます。 #### Node での実行 -以下のような webpack の設定を作成します: +以下のような webpack の設定を作成します([`.babelrc`](https://babeljs.io/docs/usage/babelrc/) もあわせて使います): ``` js +// webpack.config.js module.exports = { entry: './test.js', output: { @@ -357,14 +192,11 @@ module.exports = { exclude: /node_modules/ } ] - }, - babel: { - presets: ['es2015'] } } ``` -その後、下記コマンドを実行します: +それから下記コマンドを実行します: ``` bash webpack @@ -373,11 +205,11 @@ mocha test-bundle.js #### ブラウザでの実行 -1. `mocha-loader` をインストール -2. 上記 Webpack 設定から `entry` を `'mocha!babel!./test.js'` に変更 -3. 設定を使用して `webpack-dev-server` を開始 -4. `localhost:8080/webpack-dev-server/test-bundle` に移動 +1. `mocha-loader` をインストールする +2. 上記 Webpack 設定から `entry` を `'mocha!babel!./test.js'` に変更する +3. 設定を使用して `webpack-dev-server` を開始する +4. ブラウザで `localhost:8080/webpack-dev-server/test-bundle` を開く #### Karma + karma-webpack を使ったブラウザでの実行 -[vue-loader documentation](http://vuejs.github.io/vue-loader/en/workflow/testing.html) 内のセットアップ方法を参考にしてください。 +[vue-loader documentation](http://vue-loader.vuejs.org/en/workflow/testing.html) 内のセットアップ方法を参考にしてください。 From 7f7dd642f00a1cb1add4eb6301f93120bb957b5a Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 17:58:46 +0900 Subject: [PATCH 41/80] Copy docs/en/strict.md 1.0 to docs/ja/strict.md --- docs/ja/strict.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/ja/strict.md diff --git a/docs/ja/strict.md b/docs/ja/strict.md new file mode 100644 index 000000000..7469f3dbc --- /dev/null +++ b/docs/ja/strict.md @@ -0,0 +1,25 @@ +# Strict Mode + +To enable strict mode, simply pass in `strict: true` when creating a Vuex store: + +``` js +const store = new Vuex.Store({ + // ... + strict: true +}) +``` + +In strict mode, whenever Vuex state is mutated outside of mutation handlers, an error will be thrown. This ensures that all state mutations can be explicitly tracked by debugging tools. + +### Development vs. Production + +**Do not enable strict mode when deploying for production!** Strict mode runs a deep watch on the state tree for detecting inappropriate mutations - make sure to turn it off in production to avoid the performance cost. + +Similar to plugins, we can let the build tools handle that: + +``` js +const store = new Vuex.Store({ + // ... + strict: process.env.NODE_ENV !== 'production' +}) +``` From b53765b47d4a517f9ac81b9ca11d92a1c29b1a6a Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 18:02:18 +0900 Subject: [PATCH 42/80] Copy version 1.0 of docs/ja/strict.md --- docs/ja/strict.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/ja/strict.md b/docs/ja/strict.md index 7469f3dbc..275e085a0 100644 --- a/docs/ja/strict.md +++ b/docs/ja/strict.md @@ -23,3 +23,29 @@ const store = new Vuex.Store({ strict: process.env.NODE_ENV !== 'production' }) ``` + +# 厳格モード + +厳格( strict )モードを有効にするには、Vuex store を作成するときに、ただ `strict: true` を指定するだけです: + +``` js +const store = new Vuex.Store({ + // ... + strict: true +}) +``` + +厳格モードでは、Vuex のステートがミューテーションハンドラの外部で変更されたら、エラーを投げます。これで全てのステートの変異がデバッギングツールで明示的に追跡できることを保証します。 + +### 開発環境 vs 本番環境 + +**本番環境に対して 厳格モードを有効にしてデプロイしてはいけません!** 厳格モードでは不適切なミューテーションを検出するためにステートツリー上に対して深い監視を実行します。パフォーマンスコストを回避するために本番環境では無効にしてください。 + +プラグインと同様に、ビルドツールに処理させることができます: + +``` js +const store = new Vuex.Store({ + // ... + strict: process.env.NODE_ENV !== 'production' +}) +``` From 64286b724a10f54d5220670796075d74cb71d874 Mon Sep 17 00:00:00 2001 From: INOUE Takuya Date: Wed, 23 Nov 2016 18:08:10 +0900 Subject: [PATCH 43/80] Translate strict.md --- docs/ja/strict.md | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/docs/ja/strict.md b/docs/ja/strict.md index 275e085a0..2e0f8f09c 100644 --- a/docs/ja/strict.md +++ b/docs/ja/strict.md @@ -1,32 +1,6 @@ -# Strict Mode - -To enable strict mode, simply pass in `strict: true` when creating a Vuex store: - -``` js -const store = new Vuex.Store({ - // ... - strict: true -}) -``` - -In strict mode, whenever Vuex state is mutated outside of mutation handlers, an error will be thrown. This ensures that all state mutations can be explicitly tracked by debugging tools. - -### Development vs. Production - -**Do not enable strict mode when deploying for production!** Strict mode runs a deep watch on the state tree for detecting inappropriate mutations - make sure to turn it off in production to avoid the performance cost. - -Similar to plugins, we can let the build tools handle that: - -``` js -const store = new Vuex.Store({ - // ... - strict: process.env.NODE_ENV !== 'production' -}) -``` - # 厳格モード -厳格( strict )モードを有効にするには、Vuex store を作成するときに、ただ `strict: true` を指定するだけです: +厳格(strict)モードを有効にするには Vuex store を作成するときに、ただ `strict: true` を指定するだけです: ``` js const store = new Vuex.Store({ @@ -35,11 +9,11 @@ const store = new Vuex.Store({ }) ``` -厳格モードでは、Vuex のステートがミューテーションハンドラの外部で変更されたら、エラーを投げます。これで全てのステートの変異がデバッギングツールで明示的に追跡できることを保証します。 +厳格モードでは Vuex の状態がミューテーションハンドラの外部で変更されたら、エラーを投げるようになります。これで全ての状態変更がデバッギングツールで明示的に追跡できることが保証されます。 ### 開発環境 vs 本番環境 -**本番環境に対して 厳格モードを有効にしてデプロイしてはいけません!** 厳格モードでは不適切なミューテーションを検出するためにステートツリー上に対して深い監視を実行します。パフォーマンスコストを回避するために本番環境では無効にしてください。 +**本番環境で厳格モードを有効にしてデプロイしてはいけません!** 厳格モードでは不適切なミューテーションを検出するためにステートツリーに対して深い監視を実行します。パフォーマンスコストを回避するために本番環境では無効にしてください。 プラグインと同様に、ビルドツールに処理させることができます: From f0fce02b65ae7c2d52250c6ab318172798a38fe4 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Fri, 25 Nov 2016 19:46:48 +0900 Subject: [PATCH 44/80] Add hot-reload --- docs/ja/hot-reload.md | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 docs/ja/hot-reload.md diff --git a/docs/ja/hot-reload.md b/docs/ja/hot-reload.md new file mode 100644 index 000000000..7011a0b35 --- /dev/null +++ b/docs/ja/hot-reload.md @@ -0,0 +1,44 @@ +# ホットリローディング + +Vuex はアプリケーションの開発を行っている間のミューテーション、モジュール、アクション、ゲッターのホットリローディングをサポートします。Webpack は [Hot Module Replacement API](https://webpack.github.io/docs/hot-module-replacement.html) を使用します。Browserify では [browserify-hmr](https://github.com/AgentME/browserify-hmr/) プラグインを使用することができます。 + +ミューテーションとモジュールのホットリローディングのために、`store.hotUpdate()` API メソッドを利用する必要があります: + +``` js +// store.js +import Vue from 'vue' +import Vuex from 'vuex' +import mutations from './mutations' +import moduleA from './modules/a' + +Vue.use(Vuex) + +const state = { ... } + +const store = new Vuex.Store({ + state, + mutations, + modules: { + a: moduleA + } +}) + +if (module.hot) { + // ホットモジュールとしてアクションとモジュールを受け付けます + module.hot.accept(['./mutations', './modules/a'], () => { + // 更新されたモジュールをインポートする + // babel 6 のモジュール出力のため、ここでは .default を追加しなければならない + const newActions = require('./actions').default + const newMutations = require('./mutations').default + // 新しいアクションとミューテーションにスワップ + store.hotUpdate({ + mutations: newMutations, + modules: { + a: newModuleA + } + }) + }) +} +``` + +ホットリローディングを試してみたい場合は、[counter-hot example](https://github.com/vuejs/vuex/tree/dev/examples/counter-hot)をチェックアウトしてください。 \ No newline at end of file From f9ababeac167e8452f42f5ca3c6c8be0a53f7018 Mon Sep 17 00:00:00 2001 From: Keisuke KITA Date: Sat, 26 Nov 2016 08:57:22 +0900 Subject: [PATCH 45/80] Translate plugins --- docs/ja/plugins.md | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 docs/ja/plugins.md diff --git a/docs/ja/plugins.md b/docs/ja/plugins.md new file mode 100644 index 000000000..d1b40008d --- /dev/null +++ b/docs/ja/plugins.md @@ -0,0 +1,120 @@ +# プラグイン + +Vuex ストア は、各ミューテーションへのフックを公開する `plugins` オプションを受け付けます。 Vuex プラグインは、単一の引数としてストアを受けつけるただの関数です: + +``` js +const myPlugin = store => { + // ストアが初期化されたときに呼ばれる + store.subscribe((mutation, state) => { + // それぞれのミューテーションの後に呼ばれる。 + // ミューテーションは { type, payload } の形式で提供されます + }) +} +``` + +そして、このように利用することができます: + +``` js +const store = new Vuex.Store({ + // ... + plugins: [myPlugin] +}) +``` + +### プラグイン内でのミューテーションのコミット + +プラグインは直接、状態を変更できません。これはコンポーネントに似ています。プラグインはコンポーネント同様に、ミューテーションのコミットによる変更のトリガーで状態を変更できます。 + +ミューテーションのコミットによるストアとデータソースの同期をプラグインで実現できます。 websocket データソースとストアを例にします (これは不自然な例です。実際には、さらに複雑なタスクのために `createPlugin` 関数は、追加でいくつかのオプションを受け取れます): + +``` js +export default function createWebSocketPlugin (socket) { + return store => { + socket.on('data', data => { + store.commit('RECEIVE_DATA', data) + }) + store.subscribe((mutation) => { + if (mutation.type === 'UPDATE_DATA') { + socket.emit('update', mutation.payload) + } + }) + } +} +``` + +``` js +const plugin = createWebSocketPlugin(socket) + +const store = new Vuex.Store({ + state, + mutations, + plugins: [plugin] +}) +``` + +### 状態のスナップショットを撮る + +時々、状態の"スナップショット"を撮って、ミューテーション前後の状態を比較したくなることがあるでしょう。それを実現するために、状態オブジェクトのディープコピーを行う必要があります: + +``` js +const myPluginWithSnapshot = store => { + let prevState = _.cloneDeep(store.state) + store.subscribe((mutation, state) => { + let nextState = _.cloneDeep(state) + + // 以前の状態と以後の状態を比較... + + // 次のミューテーションのために状態を保存 + prevState = nextState + }) +} +``` + +**状態のスナップショットを撮るプラグインはアプリケーションの開発の間だけ使われるべきです。** Webpack や Browserify を使っていれば、ビルドツールにそれを処理させることができます: + +``` js +const store = new Vuex.Store({ + // ... + plugins: process.env.NODE_ENV !== 'production' + ? [myPluginWithSnapshot] + : [] +}) +``` + +上のように記述すれば、プラグインはデフォルトで利用されることになります。本番環境( production ) では、 `process.env.NODE_ENV !== 'production'` を `false` に置き換えるために、 Webpack では[DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin) 、 Browserify では[envify](https://github.com/hughsk/envify) が必要になります。 + +### ビルトインロガープラグイン + +> もし、あなたが [vue-devtools](https://github.com/vuejs/vue-devtools) を使っている場合は、これは不要でしょう。 + +Vuex には、一般的なデバッグに利用する用途の備え付けのロガープラグインがあります。 + +```js +import createLogger from 'vuex/dist/logger' + +const store = new Vuex.Store({ + plugins: [createLogger()] +}) +``` + +`createLogger` 関数はいくつかのオプションを受け取ります: + +``` js +const logger = createLogger({ + collapsed: false, // ログ出力されたミューテーションを自動で展開します + transformer (state) { + // ロギングの前に、状態を変換します + // 例えば、特定のサブツリーのみを返します + return state.subTree + }, + mutationTransformer (mutation) { + // ミューテーションは、{ type, payload } の形式でログ出力されます + // 任意の方法でそれをフォーマットできます + return mutation.type + } +}) +``` + +ロガーファイルは、他にも `