props で required か default を付けないと "Object is possibly 'undefined'." になる
実践 TypeScript をあらためて読みながら触ってたら、props を呼ぶ際にエラー吐いた。
記述とエラー
<script lang="ts"> import { defineComponent, PropType } from 'vue'; export default defineComponent({ name: 'HelloWorld', props: { msg: String, obj: { type: Object as PropType<{ name: string }>, required: true }, arr: { type: Array as PropType<{ task: string }[]> } }, computed: { myName(): string { return this.obj.name }, myFirstTask(): string { return this.arr[0].task }, } }); </script>
myFirstTask の return this.arr[0].task
の箇所で "Object is possibly 'undefined'." と言われてしまう。
原因と対応
そもそも props の arr
が渡ってくるか分かっていないのが原因。
default か required プロパティを追加してあげることで解決する。
required
props: { arr: { type: Array as PropType<{ task: string }[]>, required: true } }
default
props: { arr: { type: Array as PropType<{ task: string }[]>, default: [] } }
実践編 | Vite 環境で Vue 3.0 の CompositionAPI に触れてみる
vue-router も準備したので、ページを作っていこうと思います。
定番の Todo アプリを作っていきましょう。
共通部分
CompositionApi.vue
と OptionsApi.vue
にそれぞれ処理を記述していきます。
比較するために、テンプレートは同じものにします。
共通テンプレート
<template> <div> <p></p> <!-- ここにはページ名が入る --> <form @submit.prevent="addTodo()"> <input type="text" v-model="store.text"> </form> <ul> <template v-for="(todo, index) in todos" :key="index" > <li class="list_item"> <span class="list_text" :class="{ is_done: todo.done }" > {{ todo.text }} </span> <template v-if="!todo.done"> <button @click="finishTodo(index)" > finish </button> </template> </li> </template> </ul> </div> </template> <style> .list_item { display: flex; margin-bottom: 10px; } .list_text { margin-right: 10px; } .is_done { text-decoration: line-through; } </style>
Script の記述
Options API
これまでの Vue.js の記述です。
特筆することはないかなと思います。data
と methods
を使って最小限の実装を行っています。
<script> export default { name: 'OptionsApi', data: () => ({ todos: [], text: '' }), methods: { addTodo() { this.todos.push({ text: this.text, done: false }) this.text = '' }, finishTodo(index) { this.todos[index].done = true } } } </script>
Composition API
いよいよ Composition API での記述になります。
早速ですがこのようになりました。
<script lang="ts"> import { reactive } from 'vue' interface Todo { text: string, done: boolean } interface Todos { todos: Todo[] } export default { setup() { const todos = reactive<Todo[]>([]) const store = reactive({ text: '' }) const addTodo = () => { const data: Todo = { text: store.text, done: false } todos.push(data) store.text = '' } const finishTodo = (index: number) => { todos[index].done = true } return { store, todos, addTodo, finishTodo } } } </script>
Composition API での記述内容の説明
型定義
まずは Todo の型を決めます。
一つ一つの Todo の型と、Todo をまとめた Array のほうも定義しました。
interface Todo { text: string, done: boolean } interface Todos { todos: Todo[] }
setup 関数
Composition API では setup()
で return することでテンプレートでも使用できるデータとなります。
export default { setup() { // データや関数を定義 return { // 定義したデータ・関数をreturn する } } }
おわり
Vite を使えばサクッと Vue 3 (Composition API) + TypeScript が試せることが分かりました。
Vite 自体がとても動作が軽いのと、ディレクトリ構造が複雑ではないこともあってお手軽に試すのにぴったりだと思います。
vue-router 導入編 | Vite 環境で Vue 3.0 の CompositionAPI に触れてみる
前回、Vite のインストールと TypeScript サポートしていることの確認を行いました。
実際に Composition API がどういうものであるか、Options API との違いを触りながら確認していこうと思います。
Options API とは
Composition API
を語るときに、これまでのデータの記述方法は Options API
と呼ぶようです。
Composition API RFC | Vue Composition API
コンポーネント内での記述箇所の比較図を見ると、これまで分散していた記述がスッキリとまとまったというのが分かると思います。
画像元: Vue Composition API - Logical Concerns vs. Option Types
2020/09/07時点の Vite には Vue-router は実装されていないので、まずはこれを導入していきましょう。
vue-router-next
Vue 2.0 では vue-router 3.0
が使われてきました。
Vue 3 においては vue-router 4.0
が公式になる予定です。
vue-router-next
をインストールしていきます。
GitHub - vuejs/vue-router-next: The Vue 3 official router (WIP)
$ npm i --save vue-router@v4.0.0-alpha.11
インストールを終えたら router を使用するためのファイルを src ディレクトリ内に追加します。このあと追加するページ類も先に記述しておこうと思います。
src/router.js
import { createWebHistory, createRouter } from "vue-router"; import Home from "./views/Home.vue"; import OptionsApi from "./views/OptionsApi.vue"; import CompositionApi from "./views/CompositionApi.vue"; const history = createWebHistory(); const routes = [ { path: "/", component: Home }, { path:"/options", component: OptionsApi }, { path: "/composition", component: CompositionApi }, ]; const router = createRouter({ history, routes }); export default router;
vue-router 3 までの表記とは幾分変わっています。
以前までは new VueRouter()
で各 routes を定義していたと思いますが、createRouter()
の引数 routes にパス一覧を渡すことで定義することができます。
ページ・コンポーネントの追加
src ディレクトリ内を以下のような構造にしていきたいと思います。
src/ ├ assets/ │ └ logo.png ├ components/ │ └ HelloWorld.vue ├ views/ │ ├ Home.vue │ ├ CompositionApi.vue │ └ OptionsApi.vue ├ App.vue ├ index.css ├ router.js └ main.js
- App.vue
- App.vue ファイル内で router-view を呼び出します。
- views/Home.vue
- CompositionApi.vue, OptionsApi.vue へのリンクを表示するページです。
- views/CompositionApi.vue
- Composition API で記述するページです。
- views/OptionsApi.vue
- Options API で記述するページです。
各ファイルの記述
src/App.vue
<template> <router-view /> </template> <script> export default { name: "App" }; </script>
views/Home.vue
<template> <h2>Comparison between CompositionAPI and OptionsAPI</h2> <div> <template v-for="(route, key) in routes" :key="key"> <p> <router-link :to="route.to" :class="{ active: isActive(route.to) }" > {{ route.text }} </router-link> </p> </template> </div> </template> <script> export default { name: 'HelloWorld', computed: { routes: () => [ { to: "/composition", text: "Composition API" }, { to: "/options", text: "Options API" } ] }, methods: { isActive(path) { return path === this.$route.path } } } </script>
views/CompositionApi.vue
<template> <div> <p>Composition API</p> </div> </template>
views/OptionsApi.vue
<template> <div> <p>Options API</p> </div> </template>
おわり
これで、各パスでページが表示できるようになります。
http://localhost:3000/composition
http://localhost:3000/options