Help us understand the problem. What is going on with this article?

Rails Vue.js TypeScriptでthisの型推論

More than 1 year has passed since last update.

この記事は エムスリー 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プラグインを使用)

vue-typescript_01.gif

また、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で使ってみましょう。トップページにアクセスすると、「ユーザー一覧」と表示されるページを作ってみます

config/routes.rb
Rails.application.routes.draw do
  root to: 'users#index'
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
  end
end
app/views/layouts/application.html.erb
-    <%= stylesheet_link_tag    'application', media: 'all' %>
-    <%= javascript_include_tag 'application' %>
+    <%= javascript_pack_tag 'application' %>
app/views/users/index.html.erb
<div id="app"></div>
<%= javascript_pack_tag 'users/index' %>
app/javascript/packs/users/index.js
import Vue from 'vue'
import UserIndex from '../../src/components/UserIndex.vue'

document.addEventListener('DOMContentLoaded', () => {
  new Vue(UserIndex).$mount('#app')
})
app/javascript/src/components/UserIndex.vue
<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の補完が効きません

tsconfig.json
{
  "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

app/javascript/src/types/vue-shims.d.ts
declare module "*.vue" {
    import Vue from 'vue'
    export default Vue
}

ts-loaderの設定

webpacker3系と2系で設定ファイルの構成が異なります

webpacker3系の場合

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

environment.loaders.set('typescript', {
  test: /\.ts$/,
  loader: 'ts-loader',
  options: {
    appendTsSuffixTo: [/\.vue$/]
  }
})

module.exports = environment

参考: webpacker2系の場合

config/webpack/loaders/typescript.js
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に書き換えます

app/javascript/src/components/UserIndex.vue
 <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することで型推論されるようになります

app/javascript/src/components/UserIndex.vue
 <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が補完候補に出できます !

スクリーンショット 2017-12-04 15.32.56.png

もう少しコードを追加して、thisの型推論の嬉しみを感じてみましょう。独自のinterfaceであるUserを追加して、リスト操作してみます。

app/javascript/src/components/UserIndex.vue
     <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の部分で補完が効いています!

スクリーンショット 2017-12-04 15.35.31.png

axiosを使う

最後にusersの部分をajaxでサーバーから取得するように変更してみましょう。httpリクエストにはaxiosを使うことにします

$ yarn add axios

ajaxでユーザー一覧を取得するendpointを作成します

   root to: 'users#index'
+  get 'ajax/users', to: 'ajax/users#index'
app/controllers/ajax/users_controller.rb
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で取得するように変更

app/javascript/src/components/UserIndex.vue
     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のメソッドが補完されます

スクリーンショット 2017-12-04 14.42.26.png

このようにVue.js 2.5以降ではTypeScriptを導入しやすくなっていています!現時点ではtemplateの部分の補完は効かないようですが、今後templateでも補完が効くようになれば益々TypeScript導入のメリットが増えてきますね!

maeharin
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした