OVERFLOW: AUTO;

Aiming to be modern, practical skilled front-end engineer.

props で required か default を付けないと "Object is possibly 'undefined'." になる

実践 TypeScript をあらためて読みながら触ってたら、props を呼ぶ際にエラー吐いた。

amzn.to

記述とエラー

<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'." と言われてしまう。

f:id:show-hei:20201204084052p:plain

原因と対応

そもそも 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 に触れてみる

f:id:show-hei:20200907034228p:plain

vue-router も準備したので、ページを作っていこうと思います。

定番の Todo アプリを作っていきましょう。

共通部分

CompositionApi.vueOptionsApi.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 の記述です。

特筆することはないかなと思います。datamethods を使って最小限の実装を行っています。

<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 する
    }
  }
}

おわり

f:id:show-hei:20200907033948p:plain

Vite を使えばサクッと Vue 3 (Composition API) + TypeScript が試せることが分かりました。

Vite 自体がとても動作が軽いのと、ディレクトリ構造が複雑ではないこともあってお手軽に試すのにぴったりだと思います。

vue-router 導入編 | Vite 環境で Vue 3.0 の CompositionAPI に触れてみる

f:id:show-hei:20200907022053p:plain

前回、Vite のインストールと TypeScript サポートしていることの確認を行いました。

overflow-auto.hatenablog.com

実際に Composition API がどういうものであるか、Options API との違いを触りながら確認していこうと思います。

Options API とは

Composition API を語るときに、これまでのデータの記述方法は Options API と呼ぶようです。

Composition API RFC | Vue Composition API

コンポーネント内での記述箇所の比較図を見ると、これまで分散していた記述がスッキリとまとまったというのが分かると思います。

f:id:show-hei:20200906230201p:plain

画像元: 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