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

Vue CLI 3.0 で TypeScript な Vue.js プロジェクトをつくってみる

More than 1 year has passed since last update.

この記事について

ついに Vue CLI 3.0 がリリースされました :tada:

Vue CLI 3.0 is here! – The Vue Point – Medium

Vue CLI 3.0 で Vue.js in TypeScript なプロジェクトをさくっとつくってみたので、その記録です。

3.0 では、プロジェクト作成時に TypeScript を選べるようになっており、Vue.js in TypeScript での開発に必要な諸々をまるっと入れてくれるので、新規開発で TypeScript を導入するハードルがグッと下がってるんじゃないか、と期待が膨らみます。

概要

  • Vue CLI: 3.0.0
  • TypeScript: 3.0.0
  • webpack: 4.16.5

Vue CLI 3.0 でプロジェクト作成すると TypeScript と webpack は上記のバージョンがインストールされます。

ドキュメントはこちらです。

Overview | Vue CLI 3

手順

1. Vue CLI 3 のインストール

手順はこちらに載っています( 2.x とはパッケージが別で、 @vue/cli となっているので注意が必要です)。

Installation | Vue CLI 3

$ yarn global add @vue/cli

私は、ひとまずグローバルに入れたくなかったので、適当なディレクトリを切ってそこに入れました。

$ vue -V
3.0.0

2. プロジェクトをつくる

$ vue create vue-ts-example

(プロジェクト名は適宜置き換えてください)

image.png

今回は TypeScript で書きたいので、下の「Manually select feature」を選択します。

Enter/Return すると、選択肢が出てきます。

image.png

お好みでオン/オフしてください。

以下、"class-style component syntax" を有効にしたパターンと無効にしたパターンに分けて記載します。

2.1. Use class-style component syntax? Yes にしたパターン

image.png

いくつか質問されますが、基本的にデフォルトでいいと思います。

package.json の中身

package.json
  "dependencies": {
    (snip)
    "vue-class-component": "^6.0.0",
    "vue-property-decorator": "^7.0.0",
    (snip)

この2つのパッケージが、 class-style component に必要なものです。

HelloWorld.vue の中身

デコレータを使って、クラス定義ができるようになっています。

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
</script>

2.2. Use class-style component syntax? No にしたパターン

package.json の中身

Yes にしたプロジェクトのと diff を取ると

14a15,16
>     "vue-class-component": "^6.0.0",
>     "vue-property-decorator": "^7.0.0",

この2つのパッケージがないことが分かります。

HelloWorld.vue の中身

シンプルに Vue.extend しています。

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  name: 'HelloWorld',
  props: {
    msg: String,
  },
});
</script>

3. ファイル構成

デフォルトでは以下のようになっています。

$ tree -L 2
.
├── README.md
├── babel.config.js
├── cypress.json
├── jest.config.js
├── node_modules
(...)
├── package.json
├── public
│   ├── favicon.ico
│   ├── img
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.vue
│   ├── assets
│   ├── components
│   ├── main.ts
│   ├── registerServiceWorker.ts
│   ├── router.ts
│   ├── shims-tsx.d.ts
│   ├── shims-vue.d.ts
│   ├── store.ts
│   └── views
├── tests
│   ├── e2e
│   └── unit
├── tsconfig.json
├── tslint.json
└── yarn.lock

まず目を引くのが、 build, config といったディレクトリがなくなっていることです。

デフォルトの webpack の設定は、 @vue スコープのパッケージに分散して置かれており、 node_modules/@vue の下にある cli-plugin-* とか cli-service とかで別々に定義されて webpack-chain という仕組みで連結されているようです(詳細は以下のドキュメントをご覧ください)。

Working with Webpack | Vue CLI 3

他にも色々変わってて、たとえば、サーバ実行のコマンドが

package.json
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",

から

package.json
    "serve": "vue-cli-service serve",

に変わってたり、

src/registerServiceWorker.js とかいうファイルが追加されてたり。

今回の趣旨は TypeScript なので詳しくは割愛します。

4. TypeScript 書いていく

簡単なカウンターを実装していきます。

コンポーネントの記述については "Use class-style component syntax?" に Yes と答えたパターンと No と答えたパターンがあります。

4.1. Template

<template>
    <div>
      <p>{{ counter }}</p>
      <button @click="handleClick">Up</button>
    </div>
  </div>
</template>

4.2. Component

4.2.1. Use class-style component syntax? Yes にしたパターン

@Mutation デコレータを使いたかったので、vue-class パッケージを追加しました。

kaorun343/vue-property-decorator: Vue.js and Property Decorator

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { mapGetters, mapMutations } from 'vuex'
import { Mutation } from 'vuex-class'

@Component({
  computed: {
    ...mapGetters({ counter: 'current' })
  },
  methods: {
    ...mapMutations(['increment'])
  }
})
export default class HelloWorld extends Vue {
  @Prop() private value!: number
  @Mutation('increment') increment!: () => void

  handleClick () {
    this.increment()
  }
}
</script>

4.2.2. Use class-style component syntax? No にしたパターン

<script lang="ts">
import Vue, { ComponentOptions } from 'vue';
import { mapGetters, mapMutations } from 'vuex'

interface HelloWorldComponent extends Vue {
  counter: number,
  increment: () => void
}

export default {
  name: 'HelloWorld',
  props: {
    msg: String,
  },
  computed: {
    ...mapGetters({ counter: 'current' })
  },
  methods: {
    ...mapMutations(['increment']),
    handleClick () {
      this.increment()
    }
  }
} as ComponentOptions<HelloWorldComponent>

4.3. Store

Store は interface つくってから、オブジェクトに型を割り当てることもできますが、今回は直接 GetterTree<S, R> などの Vuex で定義されている型を指定しました。

import Vue from 'vue';
import Vuex, { StoreOptions, GetterTree, MutationTree, ActionTree } from 'vuex';

Vue.use(Vuex);

interface CounterState {
  counter: number
}

const state: CounterState = {
  counter: 1
}

const getters: GetterTree<CounterState, CounterState>  = {
  current (state: CounterState): number {
    return state.counter
  }
}

const mutations: MutationTree<CounterState> = {
  increment (state: CounterState): void {
    state.counter++
  }
}

const actions: ActionTree<CounterState, CounterState> = {}

const store: StoreOptions<CounterState> = {
  state,
  getters,
  mutations,
  actions,
}

export default new Vuex.Store<CounterState>(store)

4.4. Router

Router も TS で書かれてましたが、今回は手付かずです(そのまま載せておきます)。

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
    },
  ],
});

おわりに

コマンド叩いてポチポチするだけであまりに簡単に Vue.js in TypeScript なプロジェクトの雛形ができてしまって感動しました。

短時間で雑に書いたので、間違いがあるかもしれません。何かあればコメント欄にてご指摘いただけると助かります :bow:

nunulk
PHP, Laravel, オブジェクト指向プログラミング, デザインパターン, リファクタリング, 関数プログラミング, etc.
http://nunulk.hatenablog.com
phper-oop
ペチオブはオブジェクト指向ワーキンググループです。様々なエンジニアの方に参加頂いております。
https://phper-oop.connpass.com/
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
ユーザーは見つかりませんでした