Hy-Vue-Adminのログインロジックをカプセル化する際、ログイン状態の管理には最初は直感的なグローバルクッキーの使用を試みましたが、書くのが不自然で面倒でした。成熟したバックエンド管理テンプレートのログインロジックを参考に、Vue の公式で推奨されている Vuex を使用してグローバルな状態管理をすることにしました:
Vuex は、Vue.js アプリケーションのための状態管理パターンです。アプリケーションのすべてのコンポーネントの状態を集中的に管理し、状態が予測可能な方法で変化するように対応します。 —— 公式定義
使用理由#
- Vue でシングルページアプリケーションを開発する際、コンポーネント間で共有するデータや状態を操作する必要があります:
- アプリケーションの規模が小さい場合、props やイベントなどの一般的な親子コンポーネント間の通信方法、単方向データフローを使用することができます
- アプリケーションの規模が大きい場合、複数のコンポーネントが同じ状態に依存している場合、単方向データフローのシンプルさが壊れる可能性があります:
- 複数のビューが同じ状態に依存している
- 異なるビューの動作が同じ状態を変更する必要がある
- 問題に対する従来の解決策:
- 問題 1 に対して:多層のネストされたコンポーネントでのパラメータ渡しは非常に煩雑になり、兄弟コンポーネント間の状態伝達を処理することができません
- 問題 2 に対して:多くのコンポーネント間の状態変更と同期のために、親子コンポーネントの直接参照やイベントを使用することがよくありますが、このモデルは非常に効率が悪く、メンテナンス不能なコードにつながる可能性があります
- 新しいアイデア:
- コンポーネントの共有状態を抽出し、グローバルなシングルトンモデルとして管理する
- コンポーネントツリーのどの位置にあっても、どのコンポーネントからでも状態を直接取得したり、アクションをトリガーしたりすることができる
- 状態管理のさまざまな概念を定義し、隔離することにより、コードはより構造化され、メンテナンスしやすくなります
まずは公式の図をご覧ください~~
コアコンセプト#
State#
単一の状態ツリーの考え方で、アプリケーションには 1 つのストアインスタンスのみが含まれます
-
Vuex は、store オプションを使用して状態をルートコンポーネントからすべての子コンポーネントに注入します(
Vue.use(Vuex)
):const app = new Vue({ el: '#app', store, // storeオブジェクトをstoreオプションに提供します components: { Counter } })
-
Vue コンポーネントで Vuex の状態を取得するには、子コンポーネントは
this.$store
を使用しますconst Counter = { template: `<div>{{ Count }}</div>`, computed: { count () { return this.$store.state.count } } }
-
mapState ヘルパー関数とオブジェクトの展開演算子
-
コンポーネントはまだローカルな状態を持っています
- Vuex を使用する必要があるということは、すべての状態を Vuex に入れる必要はないということです
- 一部の状態が厳密に単一のコンポーネントに属している場合は、それをコンポーネントのローカルな状態として保持するのが最善です
Mutation#
Vuex のストア内の状態を変更する唯一の方法は、ミューテーションをコミットすることです:
- 各ミューテーションには、文字列のイベントタイプ(type)とコールバック関数(handler)があります。コールバック関数は、実際の状態変更が行われる場所であり、デフォルトで state を最初のパラメータとして受け取ります
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++
}
}
})
- ミューテーションハンドラを直接呼び出すことはできません。イベント登録の考え方に従ってください:
increment
タイプのミューテーションをトリガーしたときにこの関数を呼び出します
store.commit('increment')
- ペイロードの提出:
store.commit
に追加のパラメータを渡すことができます - ミューテーションは同期関数でなければなりません:ミューテーションイベントタイプによって引き起こされる状態の変更は、その時点で完了する必要があります
Action#
ミューテーションと似ていますが、以下の違いがあります:
- アクションは状態を直接変更するのではなく、ミューテーションをコミットします
- アクションには任意の非同期操作を含めることができます
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
アクション関数は、ストアインスタンスと同じメソッドとプロパティを持つコンテキストオブジェクトを受け取ります。つまり、context.commit
を使用してミューテーションをコミットすることができます
-
アクションは
store.dispatch
メソッドを使用してトリガーされます:store.dispatch('increment')
Module#
単一の状態ツリーを使用するため、アプリケーションのすべての状態が大きなオブジェクトに集中する可能性があり、ストアオブジェクトが肥大化する可能性があります
そのため、Vuex ではストアをモジュールに分割し、各モジュールには独自の state、mutation、action、さらにはネストされたサブモジュールが含まれることができます
解決策#
src ディレクトリにグローバルな状態管理のコードを書きます。その中には user の状態も含まれます
src
|—— api
|—— login.js # ユーザーログインAPIインターフェース
|—— ……
|—— ……
|—— store
|—— modules
|—— user.js # ストア内のuserモジュール
|—— getters.js
|—— index.js
|—— utils
|—— auth.js # ユーザートークンに関連する操作
|—— request.js # ログインリクエストのインターセプター
store 内の user モジュール:
Login.vue でログインをクリックしてアクション Login をディスパッチします:
user モジュールのアクションはまずログイン API を呼び出し、トークンが成功した場合は state トークンを設定し、クッキーでトークンを保存します:
これでトークンの状態を保存するログインのロジックが完了しました
ログアウトのロジックも同様で、ログアウトをクリックしてアクションをディスパッチし、成功したステータスコードが返されたら state トークンを空に設定し、クッキーを削除します。実装コードをご覧ください〜