Tech Blog

Vue 3 + Vue Router + Vuex + TypeScript + Vite

Requirements

Recomendation

今回の環境

Node.js

TL;DR

  1. Vue 3 + TypeScript + Vite

my-vue-app プロジェクト作成

yarn create vite my-vue-app --template vue-ts
cd my-vue-app
yarn

alias 追加

yarn add -D @types/node@^16.11.15
  // ./tsconfig.json

  {
    "compilerOptions": {
      "target": "esnext",
      "useDefineForClassFields": true,
      "module": "esnext",
      "moduleResolution": "node",
      "strict": true,
      "jsx": "preserve",
      "sourceMap": true,
      "resolveJsonModule": true,
      "esModuleInterop": true,
      "lib": ["esnext", "dom"]
+     "baseUrl": "./",
+     "paths": {
+       "@/*": ["./src/*"]
+     },
    },
    "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
  }
  // ./vite.config.ts

+ import { resolve } from 'path'
  import { defineConfig } from 'vite'
  import vue from '@vitejs/plugin-vue'

  // https://vitejs.dev/config/
  export default defineConfig({
+   base: "./",
+   resolve: {
+     alias: {
+       "@": resolve(__dirname, "./src")
+     }
+   },
    plugins: [vue()]
  })
  1. Vue Router

Vue Router](https://next.router.vuejs.org/installation.html) 追加

yarn add vue-router@next

router 追加

  // ./src/router/index.ts

+ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
+
+ const getRoutes = () => {
+   const { routes } = loadRouters()
+   return routes
+ }
+
+ const router = createRouter({
+   history: createWebHistory(),
+   routes: getRoutes()
+ })
+
+ export default router
+
+ function loadRouters () {
+   const context = import.meta.globEager("../views/**/*.vue")
+   const routes: RouteRecordRaw[] = []
+
+   Object.keys(context).forEach((key: any) => {
+     if (key === "./index.ts") return
+     let name = key.replace(/(\.\.\/views\/|\.vue)/g, '')
+     let path = "/" + name.toLowerCase()
+     if (name === "Index") path = "/"
+     routes.push({
+       path: path,
+       name: name,
+       component: () => import(`../views/${name}.vue`)
+     })
+   })
+
+   return { context, routes }
+ }
+
  // ./src/App.vue

- <script setup lang="ts">
- // This starter template is using Vue 3 <script setup> SFCs
- // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
- import HelloWorld from './components/HelloWorld.vue'
- </script>
-
  <template>
-   <img alt="Vue logo" src="./assets/logo.png" />
-   <HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
+ 	<router-view />
  </template>
  ...
  // ./src/main.ts

  import { createApp } from 'vue'
  import App from './App.vue'
+ import router from './router'

- createApp(App).mount('#app')
+ createApp(App).use(router).mount('#app')

サンプル

  // ./src/views/Index.vue

+ <script setup lang="ts">
+ import HelloWorld from '@/components/HelloWorld.vue'
+ </script>
+
+ <template>
+   <img alt="Vue logo" src="@/assets/logo.png" />
+   <HelloWorld msg="Hello Vue 3 + Vue Router + TypeScript + Vite" />
+ </template>
+
  1. Vuex

Vuex 追加

yarn add vuex@next

store 追加

  // ./src/store/modules/index.ts

+ export const { context, modules } = loadModules();
+
+ export function loadModules() {
+   const context = import.meta.globEager("./*.ts") as AnyObject;
+   const modules: AnyObject = {};
+   Object.keys(context).forEach((key: string) => {
+     if (key === "./index.ts") return;
+     modules[key.replace(/(\.\/|\.ts)/g, '')] = context[key].default
+   });
+
+  return { context, modules }
+ }
+
  // ./src/store/index.ts

+ import { InjectionKey } from 'vue'
+ import { createStore, useStore as baseUseStore, Store } from 'vuex'
+ import { context, modules, loadModules } from './modules'
+
+ export const key: InjectionKey<Store<State>> = Symbol()
+
+ export interface State {
+   [key: string]: any
+ }
+
+ const store = createStore({
+   modules,
+ })
+
+ export function useStore() {
+   return baseUseStore()
+ }
+
+ if (import.meta.hot) {
+   import.meta.hot?.accept(Object.keys(context), () => {
+     const { modules } = loadModules()
+     store.hotUpdate({
+         modules
+     })
+   })
+ }
+
+ export default store
+
  // ./src/main.ts
  import { createApp } from 'vue'
  import App from './App.vue'
+ import router from './router'
+ import store from './store'

- createApp(App).use(router).mount('#app')
+ createApp(App).use(router).use(store).mount('#app')

  // ./src/vuex.d.ts

+ import { ComponentCustomProperties } from 'vue'
+ import { Store } from 'vuex'
+
+ declare module '@vue/runtime-core' {
+   interface State {
+     [key: string]: any
+   }
+   interface ComponentCustomProperties {
+     $store: Store<State>
+   }
+ }
+

サンプル

  // ./src/store/modules/counter.ts

+ import { Module } from "vuex";
+
+ interface StoreCounter {
+   count: number
+ }
+
+ const store: Module<StoreCounter, unknown> = {
+   namespaced: true,
+   state() {
+     return {
+       count: 0
+     }
+   },
+   mutations: {
+     increment(state: StoreCounter, payload: number) {
+       state.count += payload;
+     }
+   },
+   actions: {
+     increment(context, payload?: number) {
+       context.commit("increment", payload ?? 1);
+     }
+   },
+   getters: {
+     count(state: StoreCounter) {
+       return state.count
+     }
+   }
+ }
+
+ export default store
+
  // ./src/components/HelloWorld.vue

  <script setup lang="ts">
- import { ref } from 'vue'
+ import { computed } from 'vue'
+ import { useStore } from '@/store'

  defineProps<{ msg: string }>()

+ const store = useStore();
+
- const count = ref(0)
+ let count = computed(() => { return store.getters["counter/count"] })
+
+ const increment = () => {
+   store.dispatch("counter/increment", 1, { root: true })
+ }
  </script>

  <template>
  ...
-   <button type="button" @click="count++">count is: {{ count }}</button>
+   <button type="button" @click="increment">count is: {{ count }}</button>
  ...
  </template>

  // ./src/views/Index.vue

  ...
  <template>
    <img alt="Vue logo" src="@/assets/logo.png" />
-   <HelloWorld msg="Hello Vue 3 + Vue Router + TypeScript + Vite" />
+   <HelloWorld msg="Hello Vue 3 + Vue Router + Vuex + TypeScript + Vite" />
  </template>

  1. 開発用サーバーを起動
yarn dev

参考にしたページ