準備編 | Vite 環境で Vue 3.0 の CompositionAPI に触れてみる
秋の気配とともに Vue.js の 3.0 のリリースの匂いがしてきました。
特に注目されている機能である CompositionAPI に触れてみようと思いますが、サクッと試すなら Vite でいけるよ、ってことで触れてみました。
2020/09/06現在、Vite であれば Vue 3.0 + TypeScript サポートしているので、お試し環境として最適でしょう。ただし、vue-router などアプリケーションを作る上で必要になるプラグインがなかったりするので、必要なものを適宜入れていくスタイルになります。
Vite の環境作成
基本的には公式にそって実行すれば OK です。
公式 Github リポジトリ
GitHub - vitejs/vite: Native-ESM powered web dev build tool. It's fast.
インストールから実行まで
$ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev
Vite のディレクトリ構造
project-name/ ├ node_module/ ├ public/ ├ index.html └ src/ ├ assets/ │ └ logo.png ├ components/ │ └ HelloWorld.vue ├ App.vue ├ index.css └ main.js
index.html で main.js を呼び出し
Vue のアプリケーション部分を描画する構造はシンプルで、index.html
の <div id="app"></div>
に対して、main.js
がアプリケーションをマウントさせているのが分かります。
index.html
<body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body>
main.js
import { createApp } from 'vue' import App from './App.vue' import './index.css' createApp(App).mount('#app')
アプリケーション部分
main.js
では App.vue
を呼び出して、それを index.html に描画しています。
App.vue
を見てみるとロゴ画像とコンポーネントである HelloWorld.vue
を呼び出しているのが分かります。HelloWorld.vue
内ではクリックカウンターの処理とテキストを表示しています。
TypeScript 化
Vite では TypeScript をサポートしています。試しに App.vue
のスクリプト部分を TS 化してみます。
やり方はかんたんで、script
タグに lang="ts"
という属性を追加するのみです。参考までに以下のように data
に型を与えてみました。
<template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue 3.0 + Vite" /> <p>{{ test }}</p> </template> <script lang="ts"> import HelloWorld from './components/HelloWorld.vue' interface TestData { text: string } export default { name: 'App', components: { HelloWorld }, data: (): TestData => ({ text: 'テスト文字列' }) } </script>
data
は TestData 型となっているので、型定義されていない値が入るとエディタ上でエラーを表示します。
おわり
とても簡単に準備が整いましたね。
Nuxt + TypeScript + GraphCMS でブログサイトを作る_04
前回までで Nuxt + TypeScript の基本的なセッティングを終え、簡単な ToDo アプリを作って nuxt-typed-vuex
プラグインの使い方を確認しました。
今回は GraphCMS の設定を行っていきます。
GraphCMS とは
詳しい説明は他のサイトに譲りますが、簡単に言うとこれから作るブログサイトの記事を保存するサービスです。ブログの記述は GraphCMS のウェブサイト上で行い、保存した記事データはフロントエンドからリクエストを送ることで受け取ることができます。
GraphCMS のアカウント作成
GraphCMS のサイトから Sign Up
で新規アカウント作成ページへ行きます。
Facebook, Github, Google もしくはメールアドレスでアカウントを作成します。
いくつかプリセットのテンプレートが存在するので、今回はちょうど良さそうな名前の Blog Starter
を選択します。
GraphCMS 上で使うプロジェクト名を入力し、地図で Asia (Tokyo)
(東京リージョン) を選択したら Create Project
をクリックします。
次のページでは利用するプランを聞かれますが、ここでは無料プランを選択します。
あとはしばらく時間を置くとダッシュボードへ遷移するので待ちましょう。
※ エラーなのか自動遷移しないことがありました。3~4分経っても画面が変わらないときはリロードしてみたらうまくいくかもしれません。
GraphCMS のスキーマ設定
スキーマとは
スキーマとは、一つのカテゴリに含まれる項目の組み合わせのようなものです。
カテゴリとは、例えば「著者」「記事」です。
カテゴリに含まれる項目とは、「著者」というカテゴリ内であれば「名前」「メールアドレス」「著者の書いた記事」などが該当します。
「記事」というカテゴリ内であれば「タイトル」「投稿日」「最終更新日」「アイキャッチ画像」「著者名」などです。
モデルの設定
上で書いたカテゴリを GraphCMS ではモデルと呼びます。ここでは記事のモデルを作っていきます。モデル名は Post
です。
左のサイドメニューで菱餅みたいなアイコンをクリックするとスキーマページへ遷移します。するとすでに Post
の Model が存在しているのが分かります。テンプレートから自動で作ってくれているのでこれを使いましょう。
今回は最小限の実装で済むように、以下のフィールドを削除して使います。削除したいフィールドにカーソルをもっていくと赤いボタンで Delete Field
という表示が出るのでそれをクリックします。
- Slug (記事の URL に任意の文字列を設定する目的だが不要。)
- Excerpt (記事から文字を抜粋して一覧の表示時に見せる目的だが不要。)
- Author (記事の著者名。今回は自分一人が管理者なのでそもそもいらない。)
- Tag (実装めんどくさいから消す。)
もう一点、これは好みですが Content
がリッチエディタだと使いづらいのでマークダウン記法で書けるように以下の手順で設定します。
Content
フィールドを削除する。- 右のフィールドリストから
Markdown
を選択する。 - フィールド名を
Content
にして決定。
これらを終えたら、Title、Date、Cover Image、Content、Author が残ると思うのでこれで OK です。
では早速記事を書いてみます。
コンテンツを作成
サイドメニューで、先ほどの菱餅アイコンの下に Content
を示すアイコンがあるので、それをクリックして遷移します。
Post
モデルを選択しページが変わるとすでにいくつかコンテンツが入っていることが分かります。テンプレート選択した際に作られたダミーデータです。では新たにコンテンツを加えてみましょう。
項目を入力
Title、Date、Cover Image、Content を入力しました。
Author の入力をしようとしますが、ちょっと入力の勝手が違いますね。ここでは Author を選択するか、新しく Author を作成するかの二択があります。
これは Author
モデルと紐付いているため、作成された Author
のコンテンツを選択する形式となるためです。ここでは Select Author
をクリックしてダミーデータとして登録されているものを使いましょう。
右上の Save and publish
ボタンを押してコンテンツ作成を完了させます。
API Playground でデータの確認
この後、フロントエンドから API リクエストで記事データを取得するわけですが、GraphCMS がどのようにデータを返すかを確認してみます。
Post
のコンテンツ一覧の上部の三点リーダーをクリックして Preview in Playground
を選択すると API のプレビューページへ遷移します。
遷移したページの左側にあるのが GraphQL のリクエストクエリの記法です。ページ中央にある再生ボタンみたいなのをクリックすると右側にデータが JSON で返ってきます。
今回はここまで。
Nuxt + TypeScript + GraphCMS でブログサイトを作る_03
Nuxt.js と TypeScript の導入設定はこちらから。
ブログを作ると言いつつ、Nuxt.js + TypeScript
の実装ってどうなるの?ってことを確認するために軽い Todo アプリを作っているところ。
今回は Todo データを保持する Store を作って Todo アプリを作ります。
Todo の型定義
store を追加する前に、todo の型を先に定義してしまいましょう。
~/types/todoTypes.d.ts
というファイルを追加し、下記のように記述します。
想定として、1つの Todo のタスクをオブジェクトで表現しようと考えています。Todo のタスクとして記載されるテキストと、そのタスクが完了したかを示す値を含みます。
export interface Todo { done: boolean text: string } export interface TodoState { todos: Todo[] }
interface Todo ...
では、一つの Todo タスクに含まれる情報を表しています。
interface TodoState ...
では、Todo タスクの集合リストを表しています。
Todo Store の追加
この辺は公式を参考にしつつ、作っていきます。
まずは ~/store/todo.ts
というファイルを作成します。
前回書いた通り、ここで記述した内容は後ほど ~/store/index.ts
で呼び出すことになります。
公式での Vanilla Vuex の書き方
nuxt-typed-vuex
を使わない素の状態だと以下のようになります。
// 素の vuex 型を使う。 import { GetterTree, ActionTree, MutationTree } from 'vuex' // 設定した Todo の型を使用するためインポートする。 import { Todo } from '~/types/todoTypes' export const state = () => ({ // todos は配列となり、要素は Todo 型を持つ。 todos: [] as Todo[] }) // おまじない的に入れてる。 export type RootState = ReturnType<typeof state> export const getters: GetterTree<RootState, RootState> = { todos: state => state.todos } export const mutations: MutationTree<RootState> = { ADD: (state, todo: Todo) => (state.todos.push(todo)), COMPLETE: (state, index: number) => ( state.todos.done = true ) } export const actions: ActionTree<RootState, RootState> = { add({ commit }, todoText: string) { const todo: Todo = { text: todoText, done: false } commit('ADD', todo) }, complete({ commit }, index: number) { commit('COMPLETE', index) } }
ごくごく普通の vuex に型が付いただけって感じですね。
見てもらうと分かるように vuex で定義してある GetterTree, ActionTree, MutationTree を付与する際、それぞれに型情報として RootState を渡す必要があります。
nuxt-typed-vuex での書き方
では nuxt-typed-vuex
での書き方を見ていきましょう。
import { getterTree, mutationTree, actionTree } from 'typed-vuex' import { Todo } from '~/types/todoTypes' export const state = () => ({ todos: [] as Todo[] }) export const getters = getterTree(state, { todos: state => state.todos }) export const mutations = mutationTree(state, { ADD(state, todo: Todo): void { state.todos.push(todo) }, COMPLETE(state, index: number) { state.todos.done = true } }) export const actions = actionTree({ state, mutations }, { // 返却する型を付けておく -> ここではデータを返さないので void 型 add({ commit }, todoText: string): void { const todo: Todo = { done: false, text: todoText } commit('ADD', todo) }, complete({ commit }, id: number): void { commit('COMPLETE', id) } })
見た目に大きく変わったのは、Vanilla Vuex で GetterTree などに当てていた型情報がなくなったところですね。かなりスッキリしました。
typed-vuex
は Vanilla Vuex をラッピングしており、内部でそのへんを処理してくれています。
というわけでこちらを採用して続きをやっていきます。
index.ts でインポート
先に作っておいた store/index.ts
で todo の store を読み込みます。
// store/index.ts import { getAccessorType } from 'typed-vuex' import * as todo from '~/store/todo' // <- 追加 export const state = () => ({}) export const getters = {} export const mutations = {} export const actions = {} export const accessorType = getAccessorType({ state, getters, mutations, actions, modules: { // <- 追加 todo, }, })
ページで Todo アプリ実装
動きを確認するため、シンプルな見た目で実装します。
そもそもブログを作る前の寄り道なので、見た目にこだわる必要が全くないのです。
<template> <div> <div> <label> <input type="text" v-model="todoText" > </label> <button @click="addTodo"> add </button> </div> <ul> <template v-for="(todo, index) in todos"> <li :key="index" :class="{ 'is_done': todo.done }" > {{ todo.text }} <button @click="completeTodo(index)"> Complete </button> </li> </template> </ul> </div> </template> <script lang="ts"> import Vue from 'vue' import { Todo } from '~/types/todoTypes' export default Vue.extend({ data: () => ({ todoText: '' }), computed: { todos(): Todo[] { return this.$accessor.todo.todos } }, methods: { addTodo() { this.$accessor.todo.add(this.todoText) this.todoText = '' }, completeTodo(index: number) { this.$accessor.todo.complete(index) } } }) </script> <style scoped> .is_done { text-decoration: line-through; } </style>
とてもシンプルな Todo ができました。