Vue 3 を vite で立ち上げる
Web+DB Press の雑誌の記事を写経しています。
example-vue-todo-list という名前で vite アプリ(って言えばいいのかな?)を立ち上げる。
~/github/others on ☁️ (ap-northeast-1) took 2s
❯ npm init vite-app example-vue-todo-list
Need to install the following packages:
create-vite-app
Ok to proceed? (y) y
npm WARN deprecated create-vite-app@1.21.0: create-vite-app has been deprecated. run `npm init @vitejs/app` or `yarn create @vitejs/app` instead.
Scaffolding project in ~/github/others/example-vue-todo-list...
Done. Now run:
cd example-vue-todo-list
npm install (or `yarn`)
npm run dev (or `yarn dev`)
警告メッセージを見て気づいたので、こっちをやり直す。
npm init @vitejs/app
いろいろ埋めていくとプロジェクトが一旦立ち上がる。
~/github/others on ☁️ (ap-northeast-1)
❯ npm init @vitejs/app
Need to install the following packages:
@vitejs/create-app
Ok to proceed? (y) y
✔ Project name: · example-vue-todo-app
✔ Select a framework: · vue
✔ Select a variant: · TypeScript
Scaffolding project in ~/github/others/example-vue-todo-app...
Done. Now run:
cd example-vue-todo-app
npm install
npm run dev
残りは、指示通り npm install
→ npm run dev
するとブラウザで立ち上がる。
生成されるコードを見てみる。Composition API を利用したコンポーネントが生成されている模様。無事に思ったとおり、Vue 3 で TypeScript に対応したテンプレートが作成された。
<template>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
+
<a
href="https://marketplace.visualstudio.com/items?itemName=octref.vetur"
target="_blank"
>
Vetur
</a>
or
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
(if using
<code><script setup></code>)
</p>
<p>See <code>README.md</code> for more information.</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Docs
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
</p>
<button @click="count++">count is: {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: {
type: String,
required: true
}
},
setup: () => {
const count = ref(0)
return { count }
}
})
</script>
<style scoped>
a {
color: #42b983;
}
label {
margin: 0 0.5em;
font-weight: bold;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px;
color: #304455;
}
</style>
data に定義した todo を単純に取り出すだけの実装を書く。
<template>
<ul>
<li v-for="todo in todoItems" v-bind:key="todo.id">{{ todo.text }}</li>
</ul>
</template>
<script lang="ts">
export default {
data() {
return {
todoItems: [
{ id: 1, text: 'Go out to the sea' },
{ id: 2, text: 'Invite a first member' }
]
}
}
}
</script>
Vue 2 と違うポイントだと思うけど、ルートに複数コンポーネントを設置できるようになっている?div とかでわざわざ加工必要がなくなったと思う。便利。
<template>
<input v-model="inputValue">
<button v-on:click="handleClick">
Todo を追加する
</button>
<ul>
<li v-for="todo in todoItems" v-bind:key="todo.id">{{ todo.text }}</li>
</ul>
</template>
<script lang="ts">
export default {
data() {
return {
inputValue: '',
todoItems: [
{ id: 1, text: 'Go out to the sea' },
{ id: 2, text: 'Invite a first member' }
]
}
},
methods: {
handleClick() {
this.todoItems.push({
id: this.todoItems.length + 1,
text: this.inputValue,
})
this.inputValue = ''
}
}
}
</script>
このあたりも Vue 2 とあまり使い心地は変わらなそう。リストをクリックすると、チェックマークがついて完了したという意味になる。
<template>
<input v-model="inputValue">
<button v-on:click="handleClick">
Todo を追加する
</button>
<ul>
<li v-for="todo in todoItems" v-bind:key="todo.id" v-on:click="todo.done = !todo.done">
{{ todo.text }}
<span v-if="todo.done">✔</span>
</li>
</ul>
</template>
<script lang="ts">
export default {
data() {
return {
inputValue: '',
todoItems: [
{ id: 1, text: 'Go out to the sea', done: false },
{ id: 2, text: 'Invite a first member', done: false }
]
}
},
methods: {
handleClick() {
this.todoItems.push({
id: this.todoItems.length + 1,
text: this.inputValue,
done: false
})
this.inputValue = ''
}
}
}
</script>
カーソルをポインタに変えておいて、いかにもクリックできるようにしてみるのはありかも。CSS の書き方も以前と変わらず。
<style scoped>
li {
cursor: pointer;
}
</style>
本は ECMA だったと思うけど、今回は TypeScript で実装しているので若干工夫が必要そう。Todo という type alias を追加しておいて、computed の関数の戻り値に型情報を付ける必要があった。
<template>
<input v-model="inputValue">
<button v-on:click="handleClick">
Todo を追加する
</button>
<input v-model="filterValue" placeholder="フィルタテキスト">
<ul>
<li v-for="todo in filteredTodoItems" v-bind:key="todo.id" v-on:click="todo.done = !todo.done">
{{ todo.text }}
<span v-if="todo.done">✔</span>
</li>
</ul>
</template>
<script lang="ts">
type Todo = {
id: number,
text: string,
done: boolean
}
export default {
data() {
return {
inputValue: '',
todoItems: [
{ id: 1, text: 'Go out to the sea', done: false },
{ id: 2, text: 'Invite a first member', done: false }
],
filterValue: ''
}
},
methods: {
handleClick() {
this.todoItems.push({
id: this.todoItems.length + 1,
text: this.inputValue,
done: false
})
this.inputValue = ''
}
},
computed: {
filteredTodoItems(): Todo[] {
if (!this.filterValue) {
return this.todoItems;
}
return this.todoItems.filter((todo) => {
return todo.text.includes(this.filterValue)
})
}
}
}
</script>
<style scoped>
li {
cursor: pointer;
}
</style>
Vuetify も Chakura UI も Vue 3 に対応していないようなので、Tailwind CSS を使うことにする。
いつもどおり。
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
あとはこのガイドに従って、tailwind.config.js
を書き直したり、src/index.css
を作ったりする。
Tailwind CSS をちょっと適用して多少見やすくしてみた。
<template>
<div>
<input v-model="inputValue" class="mx-4 p-2 rounded border-2 border-gray-300 focus:outline-none">
<button v-on:click="handleClick" class="py-2 px-4 rounded bg-green-500 text-white focus:ring focus:outline-none focus:ring-green-500 focus:ring-opacity-50">
Todo を追加する
</button>
</div>
<div class="my-4 drop-shadow">
<input v-model="filterValue" placeholder="フィルタテキスト" class="mx-4 p-2 rounded border-2 border-gray-300 focus:outline-none">
</div>
<ul class="mx-8 my-8 list-inside list-disc">
<li v-for="todo in filteredTodoItems" v-bind:key="todo.id" v-on:click="todo.done = !todo.done">
{{ todo.text }}
<span v-if="todo.done">✔</span>
</li>
</ul>
</template>
<script lang="ts">
type Todo = {
id: number,
text: string,
done: boolean
}
export default {
data() {
return {
inputValue: '',
todoItems: [
{ id: 1, text: 'Go out to the sea', done: false },
{ id: 2, text: 'Invite a first member', done: false }
],
filterValue: ''
}
},
methods: {
handleClick() {
this.todoItems.push({
id: this.todoItems.length + 1,
text: this.inputValue,
done: false
})
this.inputValue = ''
}
},
computed: {
filteredTodoItems(): Todo[] {
if (!this.filterValue) {
return this.todoItems;
}
return this.todoItems.filter((todo) => {
return todo.text.includes(this.filterValue)
})
}
}
}
</script>
<style scoped>
li {
cursor: pointer;
}
</style>