【nuxt-property-decorator】よく使う親子間デコレータまとめ

Nuxt.js logo

最近何かとNuxt.jsと縁があるので、スタンダードなライブラリであるnuxt-property-deocatorの中の親子間での受け渡しが捗るデコレータ群の使い方をまとめてみました。

@Prop

@Prop(変数が持つ情報) 変数という書き方でデータを受ける側の変数定義ができます。

<template>
  <button>
    <span>
      <i class="fas fa-abacus"></i>
    </span>
    <span v-if="!iconOnly">{{ label }}</span>
  </button>
</template>

<script lang="ts">
import { Vue Component, Prop } from 'nuxt-property-decorator'

@Component
export default class HogeButton extends Vue {
  @Prop({ type: Boolean, default: false })
  iconOnly: boolean

  @Prop(String)
  label: string
}
</script>

適当にそれっぽいものを書いてみました。

このコンポーネントを使用する際は、

// iconOnlyはfalse
<hoge-button :label="文字列が入った変数"></hoge-button>
// iconOnlyはtrue
<hoge-button icon-only label="何らかの文字列"></hoge-button>

このような形になります。

defaultを使用するとboolean型やenumにも対応できるので便利です。

@Emit

@Emit(属性名(@の後に付けるイベント名)) メソッド名(親に送るデータ定義) {}で親に対して値やイベントリスナーを設定できたりします。

<template>
  <button @click="emitClick(label)">
    <span>
      <i class="fas fa-abacus"></i>
    </span>
    <span v-if="!iconOnly">{{ label }}</span>
  </button>
</template>

<script lang="ts">
import { Vue Component, Prop, Emit } from 'nuxt-property-decorator'

@Component
export default class HogeButton extends Vue {
  @Prop({ type: Boolean, default: false })
  iconOnly: boolean

  @Prop(String)
  label: string

  @Emit('foo')
  emitClick(label: string) {}
}
</script>

Propのサンプルコードに付け加えました。
ボタン本来のクリックイベントが発火された際に乗じて、親に対してイベントを飛ばします。
また、引数を定義することで、実行時に子から親へ値を渡すことができます。

サンプルコードをコンポーネントとして使う場合、

// script部
value = ''

// template部
<hoge-button label="fuga" @foo="value = $event"></hoge-button>

このような形で親子で受け渡しが出来るようになります。(書き方サボってます。)

HogeButtonコンポーネントが持つlabelプロパティに文字列fugaを渡し、ボタンが押下されるとbuttonタグ本来のクリックイベントが発火し、同時にemitClickが実行されることで、fooイベントとして一緒に引数であるlabelが親に返されます。

labelは親から文字列fugaとして受けており、イベント時はそのまま親に返し、親では直接valueに代入されているので、valueには文字列fugaがそのまま代入されています。

子で定義したイベント時の引数は親側では$eventとして使用できるので、この時にHogeButton内のemitClickと同様のメソッドを用意しておけば、さらに親のコンポーネントにデータを送ることができます。バケツリレーはVueあるあるですね。

@PropSync

<custom :value=”value” @input=”value = $event”></custom>のように親から子に値を渡し、子で変更したい。しかし、子で値を変更してはいけないので、親側でイベント検知して、変更点を受け、実際に値を変更する。

単体だとPropEmitでも正直そこまで気にならなかったりするのですが、バケツリレーの階層が多くなるほど、対応が苦しくなってくるので、そんな時にはv-modelsyncといったVueで用意されているシンタックスシュガーを使用します。

PropSyncはそれらをするための準備がものすごく簡単になるデコレータです。

@PropSync(sync名, 変数情報) 変数という形で定義します。

<template>
  <input v-model="syncedValue">
</template>

<script lang="ts">
import { Vue Component, PropSync } from 'nuxt-property-decorator'

@Component
export default class HogeInput extends Vue {
  @PropSync('val', { type: String, default: '' })
  syncedValue: string
}
</script>

これを子コンポーネントとして使う場合、

// script部
value = 'bar'

// template部
<hoge-input :val.sync="value"></hoge-input>

このように使うことが可能になります。
少し前まではゲッターとセッターを都度定義していたので、めちゃくちゃ楽になりました。

@Watch

流れてくる値や定義した値が変更されたタイミングで、呼び出されるメソッドを定義できる、変数と対になったデコレータです。

@Watch(検知対象の変数名) メソッド名(検知対象のデータ定義) {}という形で使います。

<template>
  <div>
    <hoge-input :val.sync="value"></hoge-input>
    <span v-if="isOverMaxLength">文字数オーバーっすわ😥</span>
  </div>
</template>

<script lang="ts">
import { Vue Component, Watch } from 'nuxt-property-decorator'
import HogeInput from '@/components/HogeInput.vue'

@Component({
  components: {
    HogeInput
  }
})
export default class FugaForm extends Vue {
  value = ''
  isOverMaxLength = false

  @Watch('value')
  watchValue(value: string) {
    this.isOverMaxLength = value.length > 100
  }
}
</script>

PropSyncで使用したHogeInputを子に使ってみました。
処理の流れとしては定義したvalueの値が変更される毎に文字数をチェックしてます。
無理やりwatchを使った感出てるけどなんとなく雰囲気は掴めるかと。

ちなみに上の使い方だと、

  • 変更前の値が取れない
  • オブジェクトや配列の変更を検知してくれない

といった現象に見舞われます。
こういった場合には

@Watch('items', { deep: true })
watchItems(newItems: Hoge[], oldItems: Hoge[]) {}

このようにdeepプロパティを指定することで、階層が深い変数の変更検知が可能になり、第二引数を指定することで、変更前の値が取れるので差分比較が可能になります。

watchはとても便利なのですが、子コンポーネント内でPropSyncと併用する場合、特にPropSyncの値をwatchメソッド内で変更する場合、親子の主従関係を潰し兼ねないので本当に必要かどうか事前に精査が必要です。

※空いた時間に他のデコレータもまとめていきます。