この記事は エムスリー Advent Calendar 2017 の4日目の記事です。
Vue.js 2.5でTypeScript対応が強化されました。2.4以前は、Vueのデフォルトであるオブジェクトリテラルベースの構文でthisの型を推論することができませんでした(別途vue-class-component
などを使いクラスベースの構文で書く必要がありました)。しかし、Vue.js 2.5でTypeScript対応が強化され、オブジェクトリテラルベースの構文でもthisの推論がきくようになりました。see: https://jp.vuejs.org/2017/09/23/upcoming-typeScript-changes-in-vue-2.5/
例(エディタはVSCodeでVeturプラグインを使用)
また、Rails 5.1でwebpackerが統合され、webpackを使いやすくなりました。この記事では、Rails5.1(webpacker)でVue.js 2.5(with TypeScript)を使える状態するまでの手順を書きます。なお、各verは以下の通り。エディタはVSCodeを使用します(Veturプラグインを入れてます)
# 以下、検証環境
rails: 5.1.4
webpacker: 3.0.2
vue: 2.5.9
typescript: 2.6.2
VSCode: 1.18.1
Vetur: 0.11.3
node, yarnのインストール
webpackerはnodeとyarnが必須なので、入れてない場合は入れます。see: https://github.com/rails/webpacker
$ brew install node
$ brew install yarn
プロジェクト作成
railsプロジェクトを新規作成します。webpackerを有効にしてrails newし、vueのボイラープレートを生成します
$ rails new アプリの名前 --webpack --skip-turbolinks
$ cd アプリの名前
$ echo '2.4.2' > .ruby-version
$ echo "/vendor/bundle" >> ./.gitignore
$ bundle --path vendor/bundle --jobs=4
$ bundle exec rails webpacker:install:vue
起動
$ ./bin/rails s
$ ./bin/webpack-dev-server
http://localhost:3000 にアクセス
ページを作成し、Vue.jsを動かす
まずはTypeScriptを使わずに、Vue.jsを普通のjavascriptで使ってみましょう。トップページにアクセスすると、「ユーザー一覧」と表示されるページを作ってみます
Rails.application.routes.draw do
root to: 'users#index'
end
class UsersController < ApplicationController
def index
end
end
- <%= stylesheet_link_tag 'application', media: 'all' %>
- <%= javascript_include_tag 'application' %>
+ <%= javascript_pack_tag 'application' %>
<div id="app"></div>
<%= javascript_pack_tag 'users/index' %>
import Vue from 'vue'
import UserIndex from '../../src/components/UserIndex.vue'
document.addEventListener('DOMContentLoaded', () => {
new Vue(UserIndex).$mount('#app')
})
<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
title: "ユーザー覧"
}
}
}
</script>
localhost:3000にアクセスすると、画面に「ユーザー一覧」と表示され、コンソールにはHello World from Webpacker
が表示されればOKです。(Hello Worldの文は、app/javascript/packs/application.js
が出力しています)
TypeScriptを使う
では、上記のscriptの部分をTypeScriptに書き換えていきます。まずは依存ライブラリをインストール
$ yarn add typescript ts-loader
tsconfig.jsonを追加
TypeScriptの設定ファイルであるtsconfis.jsonをトップ階層に追加します。Vue.jsの公式ドキュメントにtsconfig.jsonの推奨構成が書かれているので、これを参考にします: https://vuejs.org/v2/guide/typescript.html#Recommended-Configuration
ポイントは、noImplicitThis
。これを有効にしないとthisの補完が効きません
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"target": "es5",
// strict: trueにすると、以下のオプションが全部trueになる
// --noImplicitAny, --noImplicitThis, --alwaysStrict and --strictNullChecks.
"strict": true
},
"exclude": [
"**/*.spec.ts",
"node_modules",
"vendor",
"public"
],
"compileOnSave": false
}
.vueの型定義を追加
see: https://github.com/Microsoft/TypeScript-Vue-Starter#single-file-components
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
ts-loaderの設定
webpacker3系と2系で設定ファイルの構成が異なります
webpacker3系の場合
const { environment } = require('@rails/webpacker')
environment.loaders.set('typescript', {
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/]
}
})
module.exports = environment
参考: webpacker2系の場合
module.exports = {
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/]
}
}
typescriptに書き換える
エントリポイントになるpacks配下の拡張子をjsからtsに変更します
$ mv app/javascript/packs/users/index.js app/javascript/packs/users/index.ts
scriptの部分にlang="ts"を追加し、コードをTypeScriptに書き換えます
<template>
<div>
<h1>{{ title }}</h1>
+ <p>{{ description }}</p>
</div>
</template>
-<script>
+<script lang="ts">
export default {
data() {
return {
title: "ユーザー覧"
}
+ },
+ computed: {
+ description(): string {
+ return "ユーザー一覧ページです"
+ }
}
}
</script>
scriptの部分がtypescriptとして評価されるようになりました!
thisを型推論
ただ、このままではthisの型推論がされません。Vue.extendすることで型推論されるようになります
<script lang="ts">
-export default {
+import Vue from 'vue'
+
+export default Vue.extend({
data() {
return {
title: "ユーザー覧"
@@ -14,8 +16,8 @@ export default {
},
computed: {
description(): string {
- return "ユーザー一覧ページです"
+ return `${this.title}ページです`
}
}
-}
+})
上記でthis.tとタイプすると、titleが補完候補に出できます !
もう少しコードを追加して、thisの型推論の嬉しみを感じてみましょう。独自のinterfaceであるUserを追加して、リスト操作してみます。
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
+
+ <h2>管理者ユーザー</h2>
+ <ul>
+ <li v-for="adminUser in adminUsers" :key="adminUser.id">
+ {{ adminUser.name }}
+ </li>
+ </ul>
+
+ <h2>一般ユーザー</h2>
+ <ul>
+ <li v-for="generalUser in generalUsers" :key="generalUser.id">
+ {{ generalUser.name }}
+ </li>
+ </ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
+interface User {
+ id: number
+ name: string
+ age: number
+ is_admin: boolean
+}
+
export default Vue.extend({
data() {
return {
- title: "ユーザー覧"
+ title: "ユーザー覧",
+ users: [
+ { id: 1, name: "hoge", age: 30, is_admin: true},
+ { id: 2, name: "fuge", age: 27, is_admin: false},
+ { id: 3, name: "foo", age: 22, is_admin: false},
+ ] as User[]
}
},
computed: {
description(): string {
return `${this.title}ページです`
+ },
+ adminUsers(): User[] {
+ return this.users.filter(user => user.is_admin)
+ },
+ generalUsers(): User[] {
+ return this.users.filter(user => !user.is_admin)
}
}
})
computedの中でusers.filterやuser.is_adminの部分で補完が効いています!
axiosを使う
最後にusersの部分をajaxでサーバーから取得するように変更してみましょう。httpリクエストにはaxiosを使うことにします
$ yarn add axios
ajaxでユーザー一覧を取得するendpointを作成します
root to: 'users#index'
+ get 'ajax/users', to: 'ajax/users#index'
class Ajax::UsersController < ApplicationController
def index
users = [
FakeUser.new(1, "hoge", 30, true),
FakeUser.new(2, "fuge", 27, false),
FakeUser.new(3, "foo", 22, false),
]
render json: users
end
class FakeUser < Struct.new(:id, :name, :age, :is_admin); end
end
usersをajaxで取得するように変更
data() {
return {
title: "ユーザー覧",
- users: [
- { id: 1, name: "hoge", age: 30, is_admin: true},
- { id: 2, name: "fuge", age: 27, is_admin: false},
- { id: 3, name: "foo", age: 22, is_admin: false},
- ] as User[]
+ users: [] as User[]
+ }
+ },
+ async created() {
+ try {
+ const res = await axios.get("/ajax/users")
+ this.users = res.data
+ } catch (e) {
+ alert(e)
}
},
axiosには型定義が含まれているので、import axios from 'axios'
するだけで、こんな感じでaxiosのresposeのメソッドが補完されます
このようにVue.js 2.5以降ではTypeScriptを導入しやすくなっていています!現時点ではtemplateの部分の補完は効かないようですが、今後templateでも補完が効くようになれば益々TypeScript導入のメリットが増えてきますね!