はじめに
Composition APIのVue2への導入方法と基本的な機能についての実装方法を試した。
基本的に公式のガイドおよびQiitaの既存記事の内容であるが、Vueユーザにとって当たり前(?)の部分が省略されておりVue初心者の自分にとって手探りな部分があったので、やや冗長ながら極力省略せずに実装方法を紹介する。
開発環境
- Vue CLI: 4.1.1
- Visual Studio Code: 1.40.2
- Visual Studio Code拡張機能
- Vetur: 0.22.6
- Vue 2 Snippets: 0.1.11
- Vue Peek: 1.0.2
- ESLint 1.9.1
プロジェクト作成
ベース作成
テンプレート作成
Vue CLIを使用してテンプレートを作成する。TypeScriptを導入するがクラススタイルにはしなくてよい。(どちらにせよ後でまるごと書き換える)
? Check the features needed for your project: TS, Linter, Unit
? Use class-style component syntax? No
TypeScriptアップデート(Optional)
Vue CLIで作成した場合、TypeScriptのバージョンは3.5.3になる(2019/12/10時点)ので3.7.3にアップデートした。(詳細は補足で)
Lintルール変更(Optional)
基本的には推奨設定に従うが、個人的な趣味でLintルールを一部変更した。(詳細は補足で)
以下で紹介するコードは変更後ルールに従っているのであしからず。
Composition API追加
本題であるComposition APIを利用するためにライブラリを追加する。
依存関係を追加
npm install @vue/composition-api
ライブラリの利用を宣言
APIの使用に先立ってライブラリ利用の宣言を追加する。
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api'; // ★追加
import App from './App.vue';
Vue.use(VueCompositionApi); // ★追加
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
Composition APIでの実装
ローカルな変数・関数
まずはComposition APIによるコンポーネント作成の基本形を示す。
<template>
<div id="app"><!-- ★内容変更 -->
<div>{{msg}}</div>
<button @click="changeMessage">hello</button>
</div>
</template>
<script lang="ts">
import { createComponent, ref } from '@vue/composition-api'; // ★'vue'に代えてimport
export default createComponent({ // ★export defaultの内容全面書き換え
setup: () => {
const msg = ref('Hello!');
const changeMessage = () => {
msg.value = 'What\'s up?';
};
return {
msg,
changeMessage,
};
},
});
</script>
<!-- styleは省略 -->
-
createComponent
関数の戻り値をデフォルトエクスポートする-
Vue
は直接は使用しないのでimport宣言ごと除去する
-
-
createComponent
関数の引数オブジェクト内のsetup
プロパティの内容を関数とし、Viewに使用する変数や関数をまとめて戻り値とする。- プリミティブな値は
ref
関数によりRef
型でラップする(上記コードのmsg
)
- プリミティブな値は
自作コンポーネントの利用/props/computed
次にVueらしくコンポーネントを親子関係にする。また、propsによる値の受け渡しとcomputedによる自動再計算も合わせて実装する。
子コンポーネント
props
とcomputed
を持つ子コンポーネントを作成する。
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h1>{{ upperMsg }}</h1>
</div>
</template>
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api';
interface Props {
msg: string;
}
export default createComponent({
props: {
msg: {
type: String,
default: 'Hello, world.',
},
},
setup: (props: Props) => {
const upperMsg = computed(() => props.msg.toUpperCase());
return {
upperMsg,
};
},
});
</script>
<!-- styleは省略 -->
- props
- 定義:
createComponent
関数の引数オブジェクト内のprops
プロパティにPropsとして利用する内容を記述 - Viewでの利用: 上の定義をしておけばそのまま使える
- 加工して利用:
setup
で記述する処理に使用する場合はsetup
に引数を追加する。なお、型は別途interfaceかtypeで定義しておく
- 定義:
- computed
-
createComponent
関数の引数オブジェクト内のsetup
内でcomputed
関数の引数に計算を実行する関数定義を記述する
-
親コンポーネント
App.vueを上記コンポーネントを利用するように修正する。
<template>
...
<HelloWorld :msg="msg"/><!-- ★追加 -->
...
</template>
<script lang="ts">
import { createComponent, ref } from '@vue/composition-api';
import HelloWorld from './components/HelloWorld.vue'; // ★追加
export default createComponent({
components: { // ★componentsプロパティ追加
HelloWorld,
},
setup: () => {
...
},
});
</script>
<!-- styleは省略 -->
- 子コンポーネントをimportする
-
createComponent
関数の引数オブジェクト内のcomponents
プロパティに使用するコンポーネントを列挙する - template内で子コンポーネントを使用する(従来通り)
setup内容の外部化/ライフサイクルフック
最後にComposition APIらしく、従来コンポーネントファイル内で実装する必要のあった内容の外部化を試す。ライフサイクルフックへの登録も合わせて試す。
外部setupファイル
公式サイトの例( https://vue-composition-api-rfc.netlify.com/#logic-extraction-and-reuse )に従ってマウスの位置を取得するモジュールを作成(Lintエラー解消のため一部差異あり)
import { ref, onMounted, onUnmounted } from '@vue/composition-api';
export function useMousePosition () {
const x = ref(0);
const y = ref(0);
const update = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
onMounted(() => {
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
return { x, y };
}
- exportする関数内でsetupで本来実行する処理を記述し、リアクティブなオブジェクトを戻り値とする
- ライフサイクルフックに登録する内容は
onMounted
などの対応する関数の引数として記述する
外部ファイルのインポート
HelloWorld.vueにmouse.tsをインポートする
<template>
...
<div>({{x}}, {{y}})</div><!-- ★追加 -->
...
</template>
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api';
import { useMousePosition } from '@/util/mouse'; // ★追加
...
export default createComponent({
...
setup: (props: Props) => {
const upperMsg = computed(() => props.msg.toUpperCase());
const { x, y } = useMousePosition(); // ★追加
return {
upperMsg,
x, // ★追加
y, // ★追加
};
},
});
</script>
<!-- styleは省略 -->
- mouse.tsをインポートする
-
setup
関数内で外部化した関数(上の例でのuserMousePosition
)を実行する - 戻り値があり、それをviewで利用する場合は自身の
setup
関数の戻り値に追加する - ローカルで設定したリアクティブオブジェクトと同様にテンプレート内で使用する
成果物
最終的なプログラムを実行すると以下のようになる。
参考
- Composition API RFC | Vue Composition API
- 先取りVue 3.x !! Composition API を試してみる - Qiita
- VeturにTypeScript3.7のOptional Chainingを適用してみる - Qiita
補足
TypeScriptアップデート
- ライブラリアップデート
npm install typescript@3.7.3 --save-dev
npm install @typescript-eslint/parser --save-dev
- Visual Studio Codeの設定
- tsファイル表示に右下出現するTypeScriptのバージョンをクリックして「ワークスペースのバージョンを使用」に変更する
- ワークスペース配下のsettings.jsonに以下の記述が追加される
"typescript.tsdk": "node_modules\\typescript\\lib"
- Visual Studio Codeの拡張機能の設定
- Veturをインストールしている前提(Vueを書くならほぼ必須)
- アプリケーションのsettings.jsonに以下の記述を追加する
"vetur.useWorkspaceDependencies": true
Lintルール変更
vue create
時にESLint + Standard config
を選択したが、個人的な趣味でcomma-dangle
とsemi
については独自ルールに変更。最終的に以下のようにした。
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript',
],
rules: {
'comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'never',
'exports': 'never',
'functions': 'never',
}],
'semi': ['error', 'always'],
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
ルール変更後、以下のコマンドで既存コードを一括修正。
npm run lint