【Vue.js】OptionsAPIとCompositionAPIの書き方比較

 少し前から新しいプロジェクトに途中参画することになりました。  
「vueなら以前のプロジェクトでも触ったことある！スラスラ書いて早速役に立っちゃうぞー！ 」と思っていたところ、「...あれ、data()は！？methodsもない...”const”多いし、全体的に書き方が全然ちがーう！！！」という悲しみに陥りました。

原因は、書き方が **Vue2(OptionsAPI)** か **Vue3(CompositionAPI)** かだったので、自分なりに調べて分かったことを備忘としてまとめておきます。

## :beginner:1.はじめに

・Vue2は2023年12月31日にサポートが終了しています。現在はVue3以降が主流です。  
・OptionsAPI、CompositionAPIはともにVue.jsのコンポーネントを定義するためのAPIスタイルです。  
・OptionsAPIはVue2で採用されてきた従来の方法。Vue3でもサポートはされています。  
・CompositionAPIはVue3から導入された新しい方法。  

それぞれのメリットデメリットは以下の記事がわかりやすかったです。 
https://qiita.com/karamage/items/7721c8cac149d60d4c4a

本記事では書き方の概要だけまとめます。  
「ボタンのクリック数をカウントし、クリック数に応じて異なるメッセージを表示する」機能をそれぞれの書き方で書いて比較できるようにしてみたいと思います。

## :crayon:2.OptionsAPIの書き方について

OptionsAPIでは、コンポーネントの中身を以下のようなオブジェクトに分けて定義します。  
・props     ：親コンポーネントから受け取る値。  
・data      ：コンポーネントのローカルなデータを定義する。  
・watch     ：データの変更を監視する。  
・methods   ：メソッドを定義する。  
・computed  ：計算プロパティを定義する。  
・mounted   ：コンポーネントがマウント（DOMに追加）された後に実行する処理を定義する。  
書き順には厳密な決まりはないようです。  

```JavaScript
<template>
  <div>
    <button @click="incrementCount">押してみて！</button>
    <p>ボタンが {{ clickCount }} 回押されました</p>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  props: {
    initialMessage: {
      type: String,
      default: 'ここにメッセージが表示されます'
    }
  },
  data() {
    return {
      clickCount: 0,
      message: ''
    };
  },
  computed: {
    updatedMessage() {
      if (this.clickCount % 3 === 0) {
        return 'やったー！';
      } else {
        return 'わーい！';
      }
    }
  },
  watch: {
    updatedMessage(newValue, oldValue) {
      console.log('メッセージが "${oldValue}" から "${newValue}"に変わりました。');
      this.message = newValue;
    }
  },
  methods: {
    incrementCount() {
      this.clickCount++;
    }
  },
  mounted() {
    this.message = this.initialMessage;
  }
};
</script>
```

## :paintbrush:3.CompositionAPIの書き方について

OptionsAPIでは、ref や computed をvueからインポートして使用します。  
defineComponentでコンポーネントとして定義します。  
setup関数内でコンポーネントのロジックを定義します。props を引数として受け取ります。  

```JavaScript
<template>
  <div>
    <button @click="incrementCount">押してみて！</button>
    <p>ボタンが {{ clickCount }} 回押されました</p>
    <p>{{ message }}</p>
  </div>
</template>

<script>
import { defineComponent, ref, computed, watch, onMounted } from 'vue';

export default defineComponent({
  props: {
    initialMessage: {
      type: String,
      default: 'ここにメッセージが表示されます'
    }
  },
  setup(props) {
    // deta
    const clickCount = ref(0);
    const message = ref('');

    // computed
    const updatedMessage = computed(() => {
      if (clickCount.value % 3 === 0) {
        return 'やったー！';
      } else {
        return 'わーい！';
      }
    });

    // watch
    watch(updatedMessage, (newValue, oldValue) => {
      console.log('メッセージが "${oldValue}" から "${newValue}" に変わりました。');
      message.value = newValue;
    });

    // methods
    const incrementCount = () => {
      clickCount.value++;
    };

    // mounted 相当の初期化処理 （refのデフォルト値に設定しておくなら不要）
    onMounted(() => {
      message.value = props.initialMessage;
    });

    return {
      clickCount,
      message,
      incrementCount
    };
  }
});
</script>
```

## :pen_ballpoint:4.Vue3.2以降の&lt;script setup>構文について

Vue3.2から&lt;script setup>構文が使えるようになりました。  
どういうことかというと、CompositionAPIでのsetup() 関数内に記述していた内容を&lt;script> 直下に直接記述することができるようになったようです。  
scriptタグの中にsetupを書くだけ。  
↓恩恵↓
・defineComponentでわざわざ定義しなくてOK。  
・definePropsでprops の定義。  
・ライフサイクルフックも&lt;script setup>で実行される(refのデフォルト値に設定しておく)ので、mountedフックも不要。（必要な場合もあります。ライフサイクルフックについては、[こちら](https://reisuta.com/lifecycle-hook/)　の記事がとても分かりやすかったです。

かなりシンプルな記述になりました。  
それと、Vue公式で&lt;script>⇒&lt;template>の書き順になっていたので倣っています。  


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

// Props
const props = defineProps({
  initialMessage: {
    type: String,
    default: 'ここにメッセージが表示されます'
  }
});

// state
const clickCount = ref(0);
const message = ref(props.initialMessage);

// computed
const updatedMessage = computed(() => {
  if (clickCount.value % 3 === 0) {
    return 'やったー！';
  } else {
    return 'わーい！';
  }
});

// watch
watch(updatedMessage, (newValue, oldValue) => {
  console.log('メッセージが "${oldValue}" から "${newValue}" に変わりました。');
  message.value = newValue;
});

// methods
const incrementCount = () => {
  clickCount.value++;
};

</script>

<template>
  <div>
    <button @click="incrementCount">押してみて！</button>
    <p>ボタンが {{ clickCount }} 回押されました</p>
    <p>{{ message }}</p>
  </div>
</template>
```

## :pen_fountain:5.おまけ　&lt;script setup>構文　TypeScriptで書いてみた
　VueはTypeScriptで書かれており、TypeScriptサポートも提供されています。  
　せっかくなので書いてみます。  
　・script setupタグの中に、`lang="ts"`を記述。  
　・型定義を記述する。  

```Typescript
<script setup lang="ts">
import { ref, computed, watch, defineProps } from 'vue';

interface Props {
  initialMessage?: string;
}

// props
// 今回はオブジェクトによりpropsを定義する方法で記述している。
// 型宣言でpropsを定義する方法もある。その場合はwithDefaultsでデフォルト値を設定する。
const props = defineProps<Props>({
  initialMessage: {
    type: String,
    default: 'ここにメッセージが表示されます'
  }
});

// state
const clickCount = ref(0);
const message = ref(props.initialMessage);

// computed
const updatedMessage = computed(() => {
  if (clickCount.value % 3 === 0) {
    return 'やったー！';
  } else {
    return 'わーい！';
  }
});

// watch
watch(updatedMessage, (newValue: string, oldValue: string) => {
  console.log('メッセージが "${oldValue}" から "${newValue}" に変わりました。');
  message.value = newValue;
});

// methods
const incrementCount = () => {
  clickCount.value++;
};
</script>

<template>
  <div>
    <button @click="incrementCount">押してみて！</button>
    <p>ボタンが {{ clickCount }} 回押されました</p>
    <p>{{ message }}</p>
  </div>
</template>
```

## :ramen:6.感想
とっくの昔にVue3が主流になっているので、今更感がありますが。。。  
とはいえ、歴史を知るのは大事ですし、昔の名残をリファクタリングするとかのクリティカルヒットがいつの日か現れることを信じて！  
書いているのはエンジニア歴1年未満の初学者なので、間違いがあればご指摘いただけると幸いです。  

情報強者しか勝たんこの世界で生き残っていくためにも、最新の技術はいち早く取り入れるようにしていきたいです。  

## :book:7.参考
https://zenn.dev/azukiazusa/articles/676d88675e4e74  
https://zenn.dev/bractom/articles/b208940a4007c5  
https://ja.vuejs.org/guide/essentials/lifecycle#lifecycle-diagram  
https://qiita.com/__knm__/items/d3732e944f42c3cca70d  
https://qiita.com/ymym98/items/c1a7c0c51076e3c68894