takeda_san’s blog

KotlinとVRを頑張っていく方向。

Vue.js+TypeScriptで管理画面をつくる第一歩

きっかけ

Vue.js(+vue-router、vuex)をTypescriptで書いたら、すごく良かったので最初の一歩をメモとして残す。
作成するサンプルはギョームツールとしてよくある、一覧画面を作る。
publicで使いやすそうな気がするので、利用するデータをもらってくるサーバサイドのAPIはこれ。
Bitcoin Price API: Bitcoin Ticker & Exchange Rate API - Blockchain

f:id:takeda_san:20190202215950p:plain

リポジトリまるごとはこれです。

github.com

プロジェクト作成編

vue create vuets-elementui

Vue CLI v3.0.5
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Router, Vuex, Linter, Unit
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? No

これに追加でElementを入れておく。 サクッっと作れて便利。

vue add element

あと、API叩くようにfetch-apiも入れておきましょう。

yarn add fetch-api

コンポーネントの作成

色々と書き方がありそうだが、公式ページのお作法に従う。
公式にこれだけ詳しく日本語で書いてあると、始めやすくてうれしい。
TypeScript Support — Vue.js

そのままコピペして動かすとこんなのが出るのですが、コンポーネントでtemplateを使うときにちょっとした設定を追加してる必要があります。

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

ここの通りですが、プロジェクトルートにvue.config.jsを作って設定を追加します。
(vuecliの設定ファイルです)
Configuration Reference | Vue CLI 3

module.exports = {
  runtimeCompiler: true
};

storeの追加編

Vuex、便利なので、さっきのコンポーネントに追加していきましょう。

まずは、肝心のstoreを追加。
storeにそのままActionなりGetter書いてもよさそうですが、一つのファイルが長くなりそうなので分けましょう。
するとこんな感じに。

export default new Vuex.Store({
  state: tickerListState,
  mutations: tickerListMutations,
  actions: tickerListActions,
  getters: tickerListGetters
});

Stateはこんな感じ。
内部で使う型は同じファイル内で定義したほうが、散らばらなくてよいかなと思ってます。
やっぱり型の恩恵が大きいのはStateですよね。

const tickerListState: TickerListState = {
  tickerList: []
};

export interface TickerListState {
  tickerList: Ticker[];
}

export interface Ticker {
  currency: string;
  marketPrice: MarketPrice;
}

export interface MarketPrice {
  '15m': number;
  last: number;
  buy: number;
  sell: number;
  symbol: string;
}

export default tickerListState;

Action、Getters、Mutationは特にいつも通りなので、リポジトリ参照。
vuets-elementui/src/ticker/list at master · takedasan/vuets-elementui · GitHub

REST APIアクセス追加編

ちょっとここからはVueとかTyepScriptとかの話ではなく、モジュールの分け方の話です。

バックエンドサーバとのやりとりを追加しましょう。
いつものVuex Wayの図に従うならば、Actionからアクセスすればよさそうです。
Vuex とは何か? | Vuex

ですが、Actionに直接fetchなりaxiosなりの通信処理とかリクエスト/レスポンスの変換処理を書くのが、ちょっと抵抗がある。
あまり、Actionでバックエンドと何で(REST?GraphQL?)通信するのかを意識したくない。
あとあんまりDRYじゃないですよね。

というわけで、Actionからバックエンドのアクセスまでに2層追加します。

  • Service
    Actionから直接利用されるのがここ。
    ひとつのイベント単位でまとめてAPIをDAO経由で叩く。
    引数で受け取ったオブジェクトをリクエスト用、レスポンス用のにオブジェクト変換するのもここ。
  • DAO
    バックエンドとの通信部分の担当。何で通信するかの差分をここで吸収する。
    REST APIの場合は一つのエンドポイントが一つの関数と紐づくようにする。

vuets-elementui/src/ticker at master · takedasan/vuets-elementui · GitHub

なんだか、バックエンドアプリの実装めいてきましたが、役割を分離することでコードの重複がなくなるのは今後のメンテを考えると非常によさそうです。

さいごに

TypeScriptを導入することによって、静的なチェックがさらに強力になり油断しまくりでコードが書けるようになりました。
うーん素晴らしい。