LoginSignup
23
18

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

Last updated at Posted at 2023-03-26

threeeeee.gif
仕事でもプライベートでもVue2しか書かず、Vue2一筋でVue3を食わず嫌いして勉強を先延ばしにしていたのですが「そろそろ勉強せんとな...」ということで最近やっと手をつけました。しかし最終的には「Vue3超イイネ!」となって嬉しかったので、学習した際のメモを元にチュートリアル記事にしてみました。
Vue2と比べて何が良いのか、目玉機能のCompositionAPIについてまとめましたので、同じくVue3の学習を始めたVue2好きの同志のお役に立てると幸いです😊

Vue3

2016年のVue2リリースから4年ぶりのメジャーアップデートとなったVue3は、2020年9月にリリースされました。余談ですが、リリース名はOne Pieceでした!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を利用している方には見慣れた書き方だと思います。

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の登場により、今までのオプションが全て必要なくなりました。

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ということです。

<script>
export default {
  setup () {
    // 実際のコード
    return {
      // template内で使用するプロパティ
    }
  }
};
</script>

setup()しか書かないのに、上のような雛形をいちいち書くのは面倒なので、Vue3(3.2以降)ではscript setupがサポートされました。これにより、scriptタグの中には、setup()の中身に書きたいことのみ記述することができるようになりました。
また、script setupを使用しない場合はtemplate内で使用するプロパティをsetup()の返り値としてリターンする必要がありましたが、script setupでは、この冗長な処理を省略することができます。
こちらの方がscript内がより簡潔になり可読性が上がるので、基本的にはscript setupを使用することをお勧めします。

<script setup>
// 実際のコード
</script>

尚、以下サンプルコードは全てscript setupを使用したものになります。

data

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

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

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

<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

通常の関数を定義する形で表現します。

<template>
  <button v-on:click="sayHello()">Say hello</button>
</template>

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

computed

computed()を使用することで表現します。引数として渡されたコールバック関数が実処理になります。

<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を定義する場合は以下のように記述します。

<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のキャッシュについては、こちらの記事を参照してください)。基本的には、算出プロパティにはcomputed()を使用しましょう。

const fullName = () => {
  return `${firstName.value} ${lastName.value}`;
};

props

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

オプション設定あり
<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>
オプション設定なし
<script setup>
import { defineProps } from 'vue';

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

watch

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

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

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

<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に変更してあげる必要があります。

<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への追加」という手順はなかなかの二度手間だと思っていたので、嬉しいアップデートでした。

<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(()=> {}) コンポーネントがアンマウントされた後

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

コンポーザブル関数による再利用性の向上

Vue2では、再利用可能なデータやメソッドを定義する際にmixinがよく使用されていました。しかしmixinには名前空間の共有によるプロパティ名のコンフリクトや、どのプロパティがmixinのものなのかが分かりにくいなどの問題がありました。(他にもmixinが良くないとされる理由がこちらの記事で分かりやすく説明されていました)
このmixin問題がCompositionAPIにより解決されています。

CompositionAPIでは、以下のように再利用可能な処理を定義します。変数や関数を色々定義したものをひとまとめにして返す関数を定義、エクスポートします。そして使用する側からは、この関数から必要なものを分割代入で取得して使用するといった感じです。関数名は先頭にuse~を付与するのが一般的です。この関数はコンポーザブル関数と呼びます。(公式ではComposable functionと紹介されています。いい感じの訳が見つからない...)

src/utils/formatHelper.js
export const useFormatHelper = () => {
  const fullName = (name) => {
    return `${name.firstName} ${name.lastName}`;
  };
  return { fullName }; // 公開する変数,関数をリターン
}

定義したコンポーザブル関数をコンポーネントから使用します。今回定義した関数useFormatHelperをインポートして、その実行結果からコンポーネント内で使用するものを分割代入で取り込み、使用します。

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でアプリケーションを作成することができます。

npm create vite@latest

コマンドを実行すると、以下のように対話形式のCLIが起動します。Vueのアプリケーションを作成する場合は、アプリケーションの名前使用するフレームワーク使用する言語が問われるのでそれぞれ入力/選択をしてください。選択が完了すると、{プロジェクト名}/ディレクトリが生成され、ディレクトリ配下にアプリケーションのプログラムが生成されます。

> ? Project name: › {プロジェクト名}
> ? Select a framework: › Vue
> ? Select a variant: › JavaScript または TypeScript

作成されたディレクトリに移動し、npm installを実行してください。アプリケーションを動かす為に必要なパッケージが一括インストールされます。

cd {プロジェクト名}
npm install

インストールが完了したら、開発サーバーを起動するコマンドnpm run devを実行してください。npmスクリプトにより、Viteで開発サーバーを起動するViteCLIコマンド(vite)が実行されます。

npm run dev

正常に開発サーバーの起動が完了すると、以下のようなログが出力されます。Local:に表示されたURLにブラウザでアクセスすると、作成したアプリケーションを確認することができます。アプリケーションの作成ができました!👏

> 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

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

23
18
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
23
18