Vue 3 + Vue Router + Vuex + TypeScript + Vite
takeharak
Requirements
Recomendation
今回の環境
TL;DR
- 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()]
})
- 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>
+
- 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>
- 開発用サーバーを起動
yarn dev