
Vue.js(Composition API)+TypeScriptの環境でVuex・Vue Router・axiosを使ってみる#3:Vuexで状態管理
今回の記事でも、Vue.jsのComposition APIでTypeScriptを使ったSPAを作成していきたいと思います。第3回目はVuexを使ったアプリケーションの状態管理を見ていきます。これまでのPropsやEmitのバケツリレーのようにコンポーネント間で値を受け渡しを行う場合、コンポーネントの構造が複雑になったり、ネストが深くなるとどうしても見通しが悪くなってしまいますし。ログイン状態の有無など、複数のコンポーネントを跨ぐ場合には扱いにくくなってしまいます。そこで、変数となるデータを集約して管理する方法がとられます。
Vue.jsの場合には「Vuex」というライブラリがスタンダードで、今回もそれを使った前提の内容になっています。ただ、本記事執筆段階では「Pinia」というVuexに変わる新しいライブラリも登場しているようです。公式でも推奨されているようで、こちらについてはまた別記事でまとめてみたいと思います。
これまでの開発環境やコンポーネントについては過去記事をご参考ください。本記事はこの記事の内容をベースに進めています。
・Vue.js(Composition API)+TypeScriptの環境でVuex・Vue Router・axiosを使ってみる#1:環境構築
・Vue.js(Composition API)+TypeScriptの環境でVuex・Vue Router・axiosを使ってみる#2:コンポーネント作成・PropsとEmit
それでは早速コードを見ていきます。今回の状態管理を追加するにあたって、「store.ts」という状態管理専用のスクリプトと、Vuexの型定義ファイルを新たに追加しています。(下記は一部抜粋している内容です)
........
┣ src
┗ ts
┣ main.ts
┣ store.ts <- 追加
┗ vue
┣ app.vue
┗ components
┣ ListDraft.vue
┣ ItemDraft.vue
┣ ListOfficial.vue
┣ ItemOfficial.vue
┗ UpdateButton.vue
┣ ......
┗ types
┣ vue.d.ts
┗ vuex.d.ts <- 追加
続いてVuexのモジュールをインストールしていきます。Vue.jsの3系を使う場合にはバージョンに注意して最新版をインストールしていきます。
$ npm install --save vuex@nextモジュールの方が追加されましたので、設定を行っていきます。
{
.....
"dependencies": {
.....
"vuex": "^4.0.2"
.....
},
}
まずは型定義ファイルを用意していきます。この中で状態管理で扱うストアデータの型定義を指定しておきます。
【types/vuex.d.ts】
import { ComponentCustomProperties } from 'vue';
import { Store } from 'vuex';
declare module '@vue/runtime-core' {
interface State {
.....
// storeの型定義
.....
}
interface ComponentCustomProperties {
$store: Store<State>
}
}
続いて、エントリーポイント側のスクリプトでVuexのモジュールを読み込んでいきます。合わせて後述するストアデータ用のファイルも読み込んでアプリケーション内で使えるようにしておきます。
【main.ts】
import { createApp } from 'vue';
import { store, key } from './store';
import App from './vue/app.vue';
const app = createApp({});
app.component('app', App)
.use(store, key)
.mount('#app');
最後に状態管理用のストアファイルを見ていきます。基本的にはVue.jsの2系と同様、「state」、「mutations」、「actions」、「getters」とそれぞれ定義(過去記事「Vue CLIとVuexでアプリケーションの状態変化を扱う」でも紹介しています)していく形になり、TypeScriptの場合には型づけが追加されるようになります。ただし、Vue.js3系では、ストア内のデータや処理について「this.$store」で参照せずに「useStore」でアクセスする形になりますので、useStoreを使えるようにエクスポートしておく点に注意します。
【store.ts】
import { InjectionKey } from 'vue';
import { createStore, Store, useStore as baseUseStore } from 'vuex';
interface itemInterface {
id: number,
isChecked: boolean,
title: string
}
export interface State {
draftItems: itemInterface[],
officialItems: itemInterface[]
}
export const key: InjectionKey<Store<State>> = Symbol();
export const store = createStore<State>({
state: {
draftItems: [],
officialItems: []
},
mutations: {
setDraftItems(state, payload: itemInterface[]) {
state.draftItems = payload;
},
setOfficialItems(state, payload: itemInterface[]) {
state.officialItems = payload;
}
},
actions: {
updateDraftItems({ commit }, payload: itemInterface[]) {
commit('setDraftItems', payload);
},
updateOfficialItems({ commit }, payload: itemInterface[]) {
commit('setOfficialItems', payload);
}
},
getters: {
getDraftItems(state) {
return state.draftItems;
},
getOfficialItems(state) {
return state.officialItems;
}
}
});
export const useStore = () => {
return baseUseStore(key);
}
最後にコンポーネント側でステートの値を取得したり更新処理を行なっていきます。値の取得はストアファイルの「getters」にアクセスすることで可能となります。また値の更新は「actions」の処理を「dispatch()」メソッドを使って呼び出すか、あるいは、直接コンポーネントファイル側から「commit()」メソッドで「mutations」を呼び出す形で、stateの値を更新していきます。
【app.vue】
<template>
<section>
<list-draft :props-items="store.getters.getDraftItems" />
<list-official :props-items="store.getters.getOfficialItems" />
</section>
<update-button @update-items="update()" />
</template>
<script lang="ts" setup>
import { reactive, computed } from 'vue';
import { useStore } from 'vuex';
import { key } from '../store';
import ListDraft from './components/ListDraft.vue';
import ListOfficial from './components/ListOfficial.vue';
import UpdateButton from './components/UpdateButton.vue';
interface dataInterface {
id: number, isChecked: boolean, title: string,
}
const store = useStore(key);
store.dispatch('updateDraftItems', [
{
id: 1,
isChecked: false,
title: 'チェック項目1'
},
{
id: 2,
isChecked: true,
title: 'チェック項目2'
},
{
id: 3,
isChecked: false,
title: 'チェック項目3'
},
]);
const data: { officialItems: dataInterface[] } = reactive({
officialItems: []
});
const checkedItems = computed(() => {
return store.getters.getDraftItems.filter((item: dataInterface) => {
return item.isChecked;
});
});
const update = () => {
data.officialItems = checkedItems.value;
store.dispatch('updateOfficialItems', data.officialItems);
}
</script>
状態管理を使わない場合ですと、基本的にコンポーネント側のデータ変数で値を保持しておくイメージですが、ここではストアファイルの値を呼び出したり、更新したりするため、コンポーネント側で保持するデータがすっきりしますね。
今回のサンプルではあまり影響を受けませんが、親コンポーネントからのPropsではなく子コンポーネントから直接データを取得したり、子コンポーネントから値の更新ができるようになることで、処理もシンプルになりますね。使い分けながらうまく状態管理を活用していくことも重要ですね。
(こちらの記事も合わせてどうぞ)
Vue.js(Composition API)+TypeScriptの環境でVuex・Vue Router・axiosを使ってみる#1:環境構築
Vue.js(Composition API)+TypeScriptの環境でVuex・Vue Router・axiosを使ってみる#2:コンポーネント作成・PropsとEmitcategories
- WordPress(61)
- JavaScript(49)
- Vue.js(48)
- CSS(46)
- jQuery(34)
- Nuxt.js(22)
- Sass(21)
- Gulp(17)
- webpack(16)
- PHP(14)
- HTML(14)
- GSAP(11)
- React(10)
- Vite(9)
- Pug(9)
- Laravel(8)
- Cordova(8)
- Linux(7)
- Next.js(6)
- Apache(5)
- Shopify(4)
- REST API(4)
- Node.js(4)
- Smarty(3)
- Git(3)
- JSON(3)
- その他プログラミング(3)
- Google Apps API(3)
- Bootstrap(3)
- 環境構築・システム(3)
- TypeScript(2)
- EJS(2)
- RMS(2)
- Docker(2)
- Instagram API(2)
- Excel VBA(2)
- SVG(1)
- Dart(1)
- ECカートASP(1)
- Markdown(1)
- AMP(1)
- Nginx(1)
- XML(1)
- shellscript(1)
- Ruby(1)
- MySQL(1)