【Vue3】Vue2ユーザー向けVue3入門

![threeeeee.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/524e2282-efb9-67e7-389a-00e27baa3e89.gif)
仕事でもプライベートでもVue2しか書かず、Vue2一筋でVue3を食わず嫌いして勉強を先延ばしにしていたのですが「そろそろ勉強せんとな...」ということで最近やっと手をつけました。しかし最終的には「Vue3超イイネ！」となって嬉しかったので、学習した際のメモを元にチュートリアル記事にしてみました。
Vue2と比べて何が良いのか、目玉機能のCompositionAPIについてまとめましたので、同じくVue3の学習を始めたVue2好きの同志のお役に立てると幸いです😊

## Vue3
2016年のVue2リリースから4年ぶりのメジャーアップデートとなったVue3は、2020年9月にリリースされました。余談ですが、リリース名は[One Piece](https://github.com/vuejs/core/releases/tag/v3.0.0)でした！Vueの生みの親、Evan Youはアニメ好きとして知られています。

## Vue2からの改良点
### 容量の軽量化
Vueは容量が小さいことが特徴の一つとして挙げられますが、Vue3ではこの強みが更に改良されています。
Vue2では~23KBほどあった本体容量は、Vue3では約1/2の10KBへの軽量化に成功しています。TreeShaking（使用されていないコードのバンドルを除外する機能）の導入と、グローバルAPIとヘルパーの大部分をESモジュールベースに実装し直したことが大幅なサイズ削減に繋がりました。とにかく軽くなったのです。

### パフォーマンスの向上
Vue3では、仮想DOMを利用した描画ロジックとレンダリング処理の見直しがされました。これにより、Vue2からスクリプト実行にかかる時間とメモリの使用量を1/2に削減することに成功しました。ノードツリーの中で、どのノードを再レンダリングするかを判断する処理などが大幅にアップグレードされたみたいです。とにかく早くなったのです。

### TypeScriptのサポート
TypeScriptとの相性はVue2の大きな課題でした。それを解決する為に、Vue3ではVueの実装をTypeScriptベースで全て書き直されています。これにより、より精度の高い型推論が実現します。

### CompositionAPI（setupオプションの登場）
Vue3で追加された最大の機能です。
今までは、以下のように`data`,`methods`,`computed`,`watch`といったオプションをそれぞれで定義していました。この記法をOptionsAPIと呼びます。特徴としては、メソッドは`methods`内に、ゲッターは`computed`内にといった感じできっちりとフレームワークのルールに沿ってコードを書く必要がありました。普段Vue2を利用している方には見慣れた書き方だと思います。
~~~javascript:scriptタグ内
export default {
  data () {
    return {
      firstName: 'Evan',
      lastName: 'You'
    };
  },
  computed: {
    fullName () => `${this.firstName} ${this.lastName}`
  },
  methods: {
    sayHello () {
      console.log(`Hello, ${this.fullName}!`);
    }
  }
};
~~~
そこに、Vue3から`setup`という新人オプションが追加されました。これこそがCompositionAPIをCompositionAPIたらしめる超重要機能なのです！
この新人は恐ろしく優秀で、先輩の仕事を全て1人でこなすことができます。今まで使用していた`data`,`methods`,`computed`などのオプションを全て`setup`内で表現することが可能なのです。次項からそれぞれの書き方を説明しますが、上記のプログラムは以下のように書き換えることができます。
`setup`の登場により、今までのオプションが全て必要なくなりました。
~~~diff_javascript:scriptタグ内
+ import { ref, computed } from 'vue';
export default {
+  setup () {
+    let firstName = ref('Evan');
+    let lastName = ref('You');
+   
+    const fullName = computed(() => {
+      return `${firstName.value} ${lastName.value}`
+    });
+   
+    const sayHello = () => {
+      console.log(`Hello, ${fullName}!`)
+    };
+   
+    return {
+      firstName,
+      firstName,
+      fullName,
+      sayHello
+    };
+  },
-  data () {
-    return {
-      firstName: 'Evan',
-      lastName: 'You'
-    };
-  },
-  computed: {
-    fullName () => `${this.firstName} ${this.lastName}`
-  },
-  methods: {
-    sayHello () {
-      console.log(`Hello, ${this.fullName}!`);
-    }
-  }
};
~~~
また、Vue3ではCompositionAPIは強制されるものではありません。OptionsAPIで記述することも可能です。

## CompositionAPI
### script setup
`setup()`の登場により、`data`,`methods`,`computed`などのオプションが不要になりました。つまり、CompositionAPIでスクリプトを記述する場合、基本的には以下のように`export default`の中には`setup()`しか書かなくてOKということです。
~~~vue
<script>
export default {
  setup () {
    // 実際のコード
    return {
      // template内で使用するプロパティ
    }
  }
};
</script>
~~~
`setup()`しか書かないのに、上のような雛形をいちいち書くのは面倒なので、Vue3（3.2以降）では`script setup`がサポートされました。これにより、`script`タグの中には、`setup()`の中身に書きたいことのみ記述することができるようになりました。
また、`script setup`を使用しない場合は`template`内で使用するプロパティを`setup()`の返り値としてリターンする必要がありましたが、`script setup`では、この冗長な処理を省略することができます。
こちらの方が`script`内がより簡潔になり可読性が上がるので、基本的には`script setup`を使用することをお勧めします。
~~~diff_vue
<script setup>
// 実際のコード
</script>
~~~
尚、以下サンプルコードは全て`script setup`を使用したものになります。

### data
`ref()`または`reactive()`を使用することでリアクティブなデータを表現します。

`ref()`は、オブジェクト以外の値をリアクティブにする際に使用します。注意点として、`ref`を使用して定義された変数の値には、`value`プロパティを用いてアクセスする必要があります。`template`内で参照する際は、`.value`は必要ありません。

`reactive()`は、オブジェクトをリアクティブにする際に使用します。こちらはデータを参照/更新する際に`.value`を付与する必要はありません。また、`reactive()`で定義したオブジェクトから分割代入した値に関しては、リアクティブ性が失われる点に注意してください。

~~~diff_vue
<template>
  <div>
    <button @click="countUp()">Count up</button>
    <p>Count: {{ count }}</p>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';

const person = reactive({
  firstName: 'Evan',
  lastName: 'You'
});

let count = ref(0);

const countUp = () => {
  console.log(person.firstName);
  count.value++;
};
</script>
~~~

『`ref()`は、オブジェクト以外の値をリアクティブにする際に使用します』と紹介しましたが、厳密には`ref()`を使用してオブジェクトをリアクティブにすることも可能ではあります。ですが、特定のプロパティにアクセスする際に`{変数名}.value.{アクセスするプロパティ}`という書き方をする必要があります。これは少し冗長なので、オブジェクトをリアクティブにする際は`reactive()`を使用することが推奨されています。

### methods
通常の関数を定義する形で表現します。
~~~vue
<template>
  <button v-on:click="sayHello()">Say hello</button>
</template>

<script setup>
const sayHello = () => {
  console.log('Hello!');
};
</script>
~~~

### computed
`computed()`を使用することで表現します。引数として渡されたコールバック関数が実処理になります。
~~~diff_vue
<template>
  <div>
    <p>FullName: {{ fullName }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const firstName = ref('Evan');
const lastName = ref('You');

const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`;
});
</script>
~~~
getterとsetterを定義する場合は以下のように記述します。
~~~diff_vue
<script setup>
import { ref, computed } from 'vue';

const firstName = ref('Evan');
const lastName = ref('You');

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`;
  },
  set(newVal) {
    [firstName.value, lastName.value] = newVal.split(' ')
  }
});
</script>
~~~
また、以下のように通常の関数を定義することでも、computedのゲッターとしての役割を再現をするこができますが、computedの特徴であるキャッシュの恩恵を受けることができません（computedのキャッシュについては、[こちらの記事](https://qiita.com/Yudai_35_/items/ca13a428ff66f54852ed)を参照してください）。基本的には、算出プロパティには`computed()`を使用しましょう。
~~~javascript
const fullName = () => {
  return `${firstName.value} ${lastName.value}`;
};
~~~

### props
`defineProps()`を使用することで表現します。
オプションを指定しない場合は、配列形式で名前のみ定義することができます。オプションを指定する場合には、オブジェクト形式でそれぞれのpropsの名前をキーにして、型、必須フラグ、デフォルト値、バリデーターなどを指定することができます。
基本的には、変数`props`に`defineProps()`の返り値を代入することpropsw定義し、propsにアクセスする際は`props.isDisabled`のようにアクセスします。
~~~diff_vue:オプション設定あり
<template>
  <button :disabled="props.isDisabled">Button</button>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  firstName: {
    type: String,
    required: true,
    default: 'Evan',
    validator (val) {
      return val.length > 0;
    }
  },
  isDisabled: {
    type: Boolean,
    required: true,
    default: false
  }
});
</script>
~~~

~~~diff_vue:オプション設定なし
<script setup>
import { defineProps } from 'vue';

const props = defineProps([ 'firstName', 'isDisabled' ]);
</script>
~~~


### watch
`watch()`または`watchEffetct()`を使用することで表現します。

`watch()`を使用する場合、第一引数に監視対象のプロパティ（`ref`または`reactive`によって定義されたリアクティブな値）、第二引数に監視対象に変更があった場合に実行するコールバック関数を指定します。コールバック関数の引数には、変更後の値、変更前の値が渡されます。

`watchEffetct()`を使用する場合、監視対象は指定しません。引数にはコールバック関数を渡します。このコールバック関数内は、実処理内で使用されているリアクティブな値が変更されたタイミングで発火されます。特徴として、初期レンダリング時に一度コールバック関数が実行されます。
~~~vue
<script setup>
import { ref, watch, watchEffect } from 'vue';

let name = ref('Evan');
let count = ref(0);

// count変更時に発火
watch(count, (newVal, oldVal) => {
  console.log(`"count"が${oldVal}から${newVal}に変更されました!`);
});

// 初期レンダリング時 & count変更時 & name変更時に発火
watchEffect(() => {
  console.log(`count: ${count.value}, name: ${name.value}`);
});
</script>
~~~

`props`を監視対象にする場合は、`toRefs`を使用して`ref`に変更してあげる必要があります。
~~~vue
<script setup>
import { watch, toRefs } from 'vue';

interface Props {
  someProps: boolean;
}

const props = defineProps<Props>();

const { someProps } = toRefs(props);

// count変更時に発火
watch(someProps, (newVal, oldVal) => {
  console.log(`propsの"someProps"が${oldVal}から${newVal}に変更されました!`);
});
</script>
~~~

### components
OptionsAPIでは外部コンポーネントをインポートして使用する場合、インポート&`components`への追加が必要でしたが、CompositionAPIではインポートのみで外部コンポーネントが使用可能です。
個人的に「インポート&`components`への追加」という手順はなかなかの二度手間だと思っていたので、嬉しいアップデートでした。
~~~vue
<template>
  <Button/>
</template>

<script setup>
import Button from './components/Button.vue';
</script>
~~~

### ライフサイクルフック
|  Vue2(OptionsAPI)  |  Vue3(CompositionAPI)  |  実行タイミング  |
| ---- | ---- | ---- |
|  `beforeCreate`  |  setup内で記述  |    |
|  `created`  |  setup内で記述  |    |
|  `beforeMount`  |  `onBeforeMount(()=> {})`  |  コンポーネントがマウントされる直前  |
|  `mounted`  |  `onMounted(()=> {})`  |  コンポーネントがマウントされた後  |
|  `beforeUpdate`  |  `onBeforeUpdate(()=> {})`  |  コンポーネントがリアクティブな状態変更によりDOMツリーを更新しようとする直前  |
|  `updated`  |  `onUpdated(()=> {})` |  コンポーネントがリアクティブな状態変更によりDOMツリーを更新した後  |
|  `beforeUnmount`  |  `onBeforeUnmount(()=> {})`  |  コンポーネントインスタンスがアンマウントされる直前  |
|  `unmounted`  |  `onUnmounted(()=> {})`  |  コンポーネントがアンマウントされた後  |

必要に応じてインポートして使用します。`実行タイミング`に記載された状態をフックし、それぞれのメソッドのコールバック関数が実行されます。上記は代表的なものになりますので、その他のライフサイクルフックは[公式ドキュメント](https://ja.vuejs.org/api/composition-api-lifecycle.html)を参照してください。
`beforeCreated`、`created`に関しては特別にフック関数を定義する必要がなくなりました。setup内のプログラムは画面遷移時、最初に上から順に実行されるので、今まで`beforeCreated`、`created`で行っていた処理はsetupの一番上に記述することで表現可能になりました。

## コンポーザブル関数による再利用性の向上
Vue2では、再利用可能なデータやメソッドを定義する際に`mixin`がよく使用されていました。しかし`mixin`には名前空間の共有によるプロパティ名のコンフリクトや、どのプロパティがmixinのものなのかが分かりにくいなどの問題がありました。（他にも`mixin`が良くないとされる理由が[こちらの記事](https://azukiazusa.dev/blog/vue-js-mixin/)で分かりやすく説明されていました）
この`mixin`問題がCompositionAPIにより解決されています。

CompositionAPIでは、以下のように再利用可能な処理を定義します。変数や関数を色々定義したものをひとまとめにして返す関数を定義、エクスポートします。そして使用する側からは、この関数から必要なものを分割代入で取得して使用するといった感じです。関数名は先頭に`use~`を付与するのが一般的です。この関数はコンポーザブル関数と呼びます。（公式ではComposable functionと紹介されています。いい感じの訳が見つからない...）
~~~javascript:src/utils/formatHelper.js
export const useFormatHelper = () => {
  const fullName = (name) => {
    return `${name.firstName} ${name.lastName}`;
  };
  return { fullName }; // 公開する変数,関数をリターン
}
~~~
定義したコンポーザブル関数をコンポーネントから使用します。今回定義した関数`useFormatHelper`をインポートして、その実行結果からコンポーネント内で使用するものを分割代入で取り込み、使用します。
~~~vue:components/Test.vue
<template>
  <div>
    <p>{{ fullName(name) }}</p>
    <button @click="sayHello()">Say hello</button>
  </div>
</template>

<script setup>
import { useFormatHelper } from '../utils/formatHelper.js';

const name = {
  firstName: 'Evan',
  lastName: 'You'
};

const { fullName } = useFormatHelper(); // ヘルパーから何を使用しているか分かり易い / 名前のコンフリクトが起こらない（起こる場合はエラーになる）
const sayHello = () => {
  console.log(`Hello, ${fullName(name)}!`);
};
</script>
~~~
分割代入により、どの関数/変数が外部から取り込んだものなのかが分かり易くなっています。
そして関数名や変数名が既にコンポーネント内で存在する場合は`Identifier 'fullName' has already been declared.`というエラーが出力されます。
mixinが抱えていた問題は、コンポーザブル関数を使用することで解決することができます。

## CompositionAPIの良点
### 柔軟性&可読性の向上
Vue2（OptionsAPI）では、データやメソッドを記述する場所が決まっていた為、それぞれ機能毎のロジック/プロパティの責務が散らばるという問題がありました。
しかし`setup`オプションの登場でメソッドやデータを記述する場所の制限が無くなったことにより、ロジック/プロパティの責務をそれぞれに分けて固めることができるようになりました。これは可読性を向上させ、保守性にも繋がります。


## Vue3アプリケーションの作成方法
Vue2までは、`vue create {PROJECT_NAME}`というVueCLIを使用したアプリケーションの作成が一般的でしたが、Vue3からは、アプリケーションの作成には**Vite**というビルドツールの使用が標準となりました。
Viteは従来のWebpackなどのビルドツールと比べ、pre-bundleという「必要な時に必要な分だけプログラムを読み込む」という方法により、プログラムの規模に関わらず常に開発サーバーの起動&更新が高速であることが特徴です。（Viteに関する詳しい解説はこちらの記事）

以下コマンドを実行することで、Viteでアプリケーションを作成することができます。
~~~bash
npm create vite@latest
~~~
コマンドを実行すると、以下のように対話形式のCLIが起動します。Vueのアプリケーションを作成する場合は、`アプリケーションの名前`、`使用するフレームワーク`、`使用する言語`が問われるのでそれぞれ入力/選択をしてください。選択が完了すると、`{プロジェクト名}/`ディレクトリが生成され、ディレクトリ配下にアプリケーションのプログラムが生成されます。
~~~bash
> ? Project name: › {プロジェクト名}
> ? Select a framework: › Vue
> ? Select a variant: › JavaScript または TypeScript
~~~
作成されたディレクトリに移動し、`npm install`を実行してください。アプリケーションを動かす為に必要なパッケージが一括インストールされます。
~~~bash
cd {プロジェクト名}
npm install
~~~
インストールが完了したら、開発サーバーを起動するコマンド`npm run dev`を実行してください。npmスクリプトにより、Viteで開発サーバーを起動するViteCLIコマンド（`vite`）が実行されます。
~~~bash
npm run dev
~~~
正常に開発サーバーの起動が完了すると、以下のようなログが出力されます。`Local:`に表示されたURLにブラウザでアクセスすると、作成したアプリケーションを確認することができます。アプリケーションの作成ができました！👏
~~~bash
> vite-test-app@0.0.0 dev
> vite


  VITE v4.1.4  ready in 194 ms

  ➜  Local:   http://127.0.0.1:5173/

  ➜  Network: use --host to expose
  ➜  press h to show help
~~~
![Screen Shot 2023-03-26 at 4.42.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/8ca2de22-a11e-6351-0c6f-363efdd30c7f.png)



最後までご覧いただき、ありがとうございました！