18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue2にComposition APIを追加してみた

Posted at

はじめに

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の使用に先立ってライブラリ利用の宣言を追加する。

main.ts
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によるコンポーネント作成の基本形を示す。

App.vue
<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による自動再計算も合わせて実装する。

子コンポーネント

propscomputedを持つ子コンポーネントを作成する。

HelloWorld.vue
<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を上記コンポーネントを利用するように修正する。

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エラー解消のため一部差異あり)

mouse.ts
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をインポートする

HelloWorld.vue
<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-demo.gif

参考

補足

TypeScriptアップデート

  • ライブラリアップデート
npm install typescript@3.7.3 --save-dev
npm install @typescript-eslint/parser --save-dev
  • Visual Studio Codeの設定
    • tsファイル表示に右下出現するTypeScriptのバージョンをクリックして「ワークスペースのバージョンを使用」に変更する
    • ワークスペース配下のsettings.jsonに以下の記述が追加される
.vscode/settings.json
"typescript.tsdk": "node_modules\\typescript\\lib"
  • Visual Studio Codeの拡張機能の設定
    • Veturをインストールしている前提(Vueを書くならほぼ必須)
    • アプリケーションのsettings.jsonに以下の記述を追加する
User/settings.json
"vetur.useWorkspaceDependencies": true

Lintルール変更

vue create時にESLint + Standard config を選択したが、個人的な趣味でcomma-danglesemiについては独自ルールに変更。最終的に以下のようにした。

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
18
19
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?