くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Nuxt3でSFCのsetup(Reactivity API/Composition API)

Vue3で登場したComposition APIがNuxt3のデフォルトっぽい。
ただだいぶ書き方が変わったのでそのあたりを調べたときの備忘録。

Vue2からある書き方は、Options API

<script setup>構文

新しく増えた<script setup>はこんな感じ。

Composition API | Vue.js
SFC <script setup> | Vue.js

基本的な書き方

数字をカウントアップする例だと、
data/computed/method/watchはこんな感じ。

<script lang="ts" setup>
import { ref } from "vue";

// constant
const defaultCount = 1;

// data
const count = ref(defaultCount);

// computed
const plusOne = computed(() => count.value + 1);

// watch
watch(count, (count, prevCount) => {
  console.log(`watch: ${prevCount} -> ${count}`);
});

// method
function countUp() {
  data.count++;
}
</script>

<template>
  <div>
    <div>count: {{ count }}</div>
    <div>plusOne: {{ plusOne }}</div>
    <div>
      <button @click="countUp">COUNT UP</button>
    </div>
  </div>
</template>

変数/定数

Vue3でReactivity APIが追加され、
値を監視し、変更を検知するときの扱いが変わった。

基本のリアクティビティ API | Vue.js

従来のように変数に値を代入するだけでは変更が反映されず、
Reactivity APIref()を使う必要がある。

// constant
const defaultCount = 1;

// data
const count = ref(defaultCount);
count.value = 2;
console.log(count.value);

若干、.valueを使わないと値が参照できないので、
手間がかかるが、reactive()を使うと楽になる。

// data
const count = ref(1);
const data = reactive({ count })

// `data.count` が更新される
count.value++
console.log(count.value) // 2
console.log(data.count) // 2

// `count` の ref も更新される
data.count++
console.log(data.count) // 3
console.log(count.value) // 3

computed

computedもReactivity APIを使う形に。

computed と watch | Vue.js

const count = ref(1);

// computed
const plusOne = computed(() => count.value + 1);

従来どおり、getter/setterを使う形でもOK。

const count = ref(1);

// computed: setter/getter
const plusOne = computed({
  get: () => count.value + 1,
  set: val => count.value = val - 1
})

watch

watchもリアクティブな変数が対象になる。

computed と watch | Vue.js

const count = ref(1);

// watch
watch(count, (count, prevCount) => {
  console.log(`watch: ${prevCount} -> ${count}`);
});

また、直接refではなく、getter関数でもOK。

// ゲッタを監視
const state = reactive({ count: 0 });
watch(
  () => state.count,
  (count, prevCount) => console.log(`watch: ${prevCount} -> ${count}`)
);

複数のrefを監視したい場合は、配列で指定する形。

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

ほかにも、watchEffect/watchPostEffect/watchSyncEffectが追加され、
前の値が不要な場合は、watchEffectで簡単に書けるように。

method

methodはシンプルで、関数を定義すればOK。

Props/Emits

PropsとEmitsは、それぞれdefineProps/defineEmitsで定義する。

<script lang="ts" setup>

// prop
interface Props {
  count: number;
  loading?: boolean;
}

const props = withDefaults(
  defineProps<Props>(), { loading: false }
);

// emit
interface Emits {
  (e: 'set-count', count: number): void;
}
const emits = defineEmits<Emits>();

// method
function onInput(e: InputEvent) {
  emits("set-count", Number((e.target as HTMLInputElement).value));
}
</script>

<template>
  <div class="mb-4">
    <div>{{ value }}</div>
    <input type="number" :value="props.count" @input="onInput" />
  </div>
</template>

Props

propsはdefinePropsから取得できる。
初期値も必要なときは、withDefaultsを使う感じ。

// prop
interface Props {
  count: number;
  loading?: boolean;
}

// 初期値なしのprops
const props = defineProps<Props>();

// 初期値ありのprops
const propsWithDefault = withDefaults(props, { loading: false });

Emits

emitsはdefineEmitsで定義が必要。
呼び出すときは返り値のemitsを使う。

// emit
interface Emits {
  (e: 'set-count', count: number): void;
}
const emits = defineEmits<Emits>();
emits('set-count', 1);

カスタムコンポーネントのv-model

個人的にはかなり衝撃的な破壊的変更。。

  • 破壊的変更: カスタムコンポーネントで使用する場合に、v-model のプロパティとイベントのデフォルト名が変更されます。
    • プロパティ: value -> modelValue
    • イベント: input -> update:modelValue
  • 破壊的変更: v-bind.sync 修飾子とコンポーネントmodel オプションは削除され、v-model の引数に置き換えられます。
  • 新規: 同じコンポーネントに複数の v-model バインディングが可能になりました。
  • 新規: カスタムの v-model 修飾子を作成する機能が追加されました。

<input>タグは今まで通りだけど、カスタムコンポーネントv-modelを使う場合は注意。
<input>を含むコンポーネントとかだと、computedを使うと良さそう

<script lang="ts" setup>
// props
interface Props {
  modelValue: number;
}

const props = withDefaults(
  defineProps<Props>(), { }
);

// emit
interface Emits {
  (e: 'update:modelValue', count: number): void;
}
const emits = defineEmits<Emits>();

// computed
const modelValue = computed({
  get: () => props.modelValue,
  set: (value: number) => emits('update:modelValue', value)
});

</script>

<template>
  <div class="mb-4">
    <div>{{ modelValue }}</div>
    <input type="number" v-model.number="modelValue" />
  </div>
</template>

複数のv-modelバインディングや修飾子などは便利。


以上!! SFC基本的な部分を一通り触れた気がする(*´ω`*)