(この記事は2019年7月に執筆しました。)
こんにちは、mabuiです。
5月から参画して、1人で実装を進めていたNext.js + TypeScriptのプロジェクトがリリース手前まで進んだので、プロジェクトをこなした上で身についた知見を数回に分けて共有していきます。
初回の今回はNuxt.jsのプロジェクトへのTypeScriptの導入方法から、コンポーネントでの型の扱い方、TypeScriptの書き方まで、この記事を通して自走してプロジェクトを進めれるようになることを目標に書いていきます。
Nuxt.jsのTypeScriptサポートは2019年7月現在、絶賛進行中のため、頻繁に更新されるNuxt.jsのバージョンによって方法が変わるため、作業を行う場合は公式の更新情報も参考にしてください。 https://ja.nuxtjs.org/guide/release-notes/
この記事ではv2.8.1で説明を進めていきます。
今回説明に使用したコードはこちらのリポジトリに置いています。
mabuix/nuxt_template
プロジェクトの作成・初期設定
まずはnext公式の足場ツールでプロジェクトを作成します。
1 2 |
yarn create nuxt-app nuxt_template |
対話式になるのでEnterで選択していきます。
Nuxt.js modulesとlinting toolsはspaceで両方選択します。
1 2 3 4 5 6 7 8 |
? Choose Nuxt.js modules ◉ Axios ❯◉ Progressive Web App (PWA) Support ? Choose linting tools ◉ ESLint ❯◉ Prettier |
下記が選択内容です。
1 2 3 4 5 6 7 8 9 10 11 12 |
✨ Generating Nuxt.js project in /Users/mabui/git/nuxt_template ? Project name nuxt_template ? Project description My Nuxt.js template project ? Author name mabui ? Choose the package manager Yarn ? Choose UI framework None ? Choose custom server framework None (Recommended) ? Choose Nuxt.js modules Axios, Progressive Web App (PWA) Support ? Choose linting tools ESLint, Prettier ? Choose test framework Jest ? Choose rendering mode Universal (SSR) |
全て選択してダウンロードが終われば、プロジェクト作成完了です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Successfully created project nuxt_template To get started: cd nuxt_template yarn dev To build & start for production: cd nuxt_template yarn build yarn start To test: cd nuxt_template yarn test ✨ Done in 305.43s. |
作成したプロジェクトディレクトリ配下でyarn dev
コマンドでアプリを起動して、http://localhost:3000
にアクセスできます。
次に作成されたnuxtの作業ディレクトリをsrc配下にまとめる設定を行います。
後々プロジェクトが膨らんでくると大変になるので、今のうちにやっておきましょう。
1 2 3 4 5 |
% pwd /Users/mabui/git/nuxt_template % mkdir src % mv assets components layouts middleware pages plugins static store test src |
nuxt.config.jsにsrcDirを追加します。
1 2 3 4 5 |
export default { mode: 'universal', srcDir: 'src/', // 追加 ... |
これでアプリ起動時に先ほどと同じようにアクセスできるようになります。
Jestを導入している場合、jest.config.jsのパスも変更しておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module.exports = { moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', // 変更 '^~/(.*)$': '<rootDir>/src/$1', // 変更 '^vue$': 'vue/dist/vue.common.js' }, ... 'collectCoverage': true, 'collectCoverageFrom': [ '<rootDir>/src/components/**/*.vue', // 変更 '<rootDir>/src/pages/**/*.vue' // 変更 ] } |
これで、yarn test
でテストが成功し、カバレッジも取得できる状態になります。
ではここからTypeScript用の設定を行なっていきます。
TypeScriptの導入
インストール
初めに必要なパッケージをインストールします。
1 2 3 |
yarn add -D @nuxt/typescript yarn add ts-node |
次に必要なファイルを作成します。ファイルの中身はアプリ起動時に自動生成されます。
1 2 |
touch tsconfig.json |
設定ファイル変更
次に設定ファイルであるnuxt.config.js
をTypeScript化します。
ファイル名をnuxt.config.ts
にリネームし、内容を下記に書き換えます。
nuxt.config.js
1 2 3 4 5 6 |
export default { mode: 'universal', srcDir: 'src/', ... } |
↓
nuxt.config.ts
1 2 3 4 5 6 7 8 9 10 |
import NuxtConfiguration from '@nuxt/config' const config: NuxtConfiguration = { mode: 'universal', srcDir: 'src/', ... } export default config |
コンポーネント変更
ではpages/index.vue
コンポーネントをTypeScriptに変更していきます。
2019.7現在、公式でのTypeScript サポートページではvue-property-decorator を使用したクラススタイルでの記述を推奨していますが、予定されていたVueのv3.0でのクラススタイルサポートが諦められて、今後非推奨になる可能性があるため、もう一方のVue.extend
を使用した記述を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<template> ... </template> <script> import Logo from '~/components/Logo.vue' export default { components: { Logo } } </script> <style> ... </style> |
↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<template> ... </template> <script lang="ts"> import Vue from 'vue' import Logo from '~/components/Logo.vue' export default Vue.extend({ components: { Logo } }) </script> <style> ... </style> |
Scriptタグの言語をtsに変更し、Vueのインスタンスをimportしてextendメソッドでラップするだけで単一ファイルコンポーネント内での型補完が効くようになります。
yarn dev
で起動させてみるとエラーになりました。
1 2 3 4 5 6 7 8 9 10 |
ERROR ERROR in /Users/manatonakane/git/nuxt_template/src/pages/index.vue 12:37:38 33:18 Cannot find module '~/components/Logo.vue'. 31 | <script lang="ts"> 32 | import Vue from 'vue' > 33 | import Logo from '~/components/Logo.vue' | ^ 34 | 35 | export default Vue.extend({ 36 | components: { |
TypeScriptでモジュールの捜索ができていないようなので、vue拡張子ファイルを認識するよう、TypeScriptの型拡張ファイルをsrc配下に作ります。名前は最後が.d.ts
で終わっていれば何でもいいです。
src/shims-vue.d.ts
1 2 3 4 5 |
declare module '*.vue' { import Vue from 'vue' export default Vue } |
これでエラー解消されました。
さらにvue-router
のように、型定義が不足していてエラーが発生する場合は自前で下記のように定義することが可能です。
mixin
で自作したメソッドなんかもこのように定義できます。
1 2 3 4 5 6 7 8 9 10 11 12 |
import Router from 'vue-router' declare module 'vue/types/vue' { interface Vue { $ua: { isFromPc: () => boolean }, $router: Router, mixinMethod: () => void } } |
ESLintの設定
まずtypescript用のパッケージをインストールします。
1 2 |
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser |
.eslintc.js
ファイルのpluginsに@typescript-eslint'
を追加し、parserを@typescript-eslint/parser
に変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
module.exports = { root: true, env: { browser: true, node: true }, parserOptions: { parser: '@typescript-eslint/parser' }, extends: [ 'prettier', 'prettier/vue', 'plugin:prettier/recommended', '@nuxtjs', 'plugin:nuxt/recommended' ], plugins: [ 'prettier', '@typescript-eslint' ], rules: { '@typescript-eslint/no-unused-vars': 'error' } } |
package.json
内のlint
スクリプトを下記に変更します。
1 2 3 |
"scripts": { "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ." |
これで、ESLintの設定は完了です。
後は.eslintc.js
のrules
にお好みの設定を追加してください。
ここからはtips的に開発していて必要になってくる内容を書いていきます。
コンポーネント内での型定義
1 2 |
* `propType`, `propOptions`の利用 |
コンポーネント内のdata
に型を指定するときは下記の様にas
を使用します。
1 2 3 4 |
data: () => ({ contents: [] as object[] }), |
ただ、これだとobject
のプロパティに全ての型が適合してしまい不十分なので、TypeScriptで型定義してあげましょう。
1 2 3 4 5 6 7 |
type Contents = { id: number; name: string }[] export default Vue.extend({ ... data: () => ({ contents: [] as Contents }), |
これでプロパティに型推論が効きます。
型定義を使いまわしたい場合は別ファイルに定義してexport
してあげればOKです
models/contents.ts
1 2 |
export type Contents = { id: number; name: string }[] |
1 2 3 4 5 6 7 |
import { Contents } from '~/models/Contents' export default Vue.extend({ ... data: () => ({ contents: [] as Contents }), |
コンポーネントのprops
で、type
で指定できるネイティブコンストラクタObject
,Array
(TypeScript
の型ではなくvue
の型推論機能) のTypeScript
型定義を行いたい場合は、propType
を使用します。
1 2 3 4 5 6 7 8 |
import Vue, { PropType } from 'vue' import { Contents } from '~/models/Contents' export default Vue.extend({ ... props: { contents: { type: Array as PropType<Contents>, required: true } }, |
その他String
等のネイティブコンストラクタを指定したプロパティを参照した時に下記のような型参照エラーが出た場合は、PropOptions
を使うことで解決できます。
1 2 3 4 5 6 7 8 9 |
48:10 Property 'value' does not exist on type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Record<string, any>>, Record<string, any>>'. 46 |}), 47 |mounted() { > 48 |this.value = this.defaultValue |^ 49 |}, 50 |methods: { 51 |setValue(e: Date): void { |
1 2 3 4 5 6 |
import Vue, { PropOptions } from 'vue' export default Vue.extend({ props: { id: { type: String, require: true } as PropOptions<string>, value: { type: String, require: true } as PropOptions<string>, |
jestの設定
todo
環境変数の扱い方
todo