Vuetifyでカスタムコンポーネントを作成する


こんにちは!
最近、私はVuetifyコンポーネントをカスタマイズして、デフォルトのコンポーネントのルック/フィールを持っています.この記事では、親コンポーネントからプロシージャ、イベント、スロットを継承する方法を示します.私はここで例としてVuetifyを取りました、しかし、プラクティス/出来事/スロットを提供するどんな他のUIフレームワークででも、実行は適用されることができました.それはいくつかの点で誰かの役に立つでしょう.

なぜ


基本的に主な理由は、時々カバーするユースケースを持っており、Vuetifyコンポーネント(または他のUIフレームワーク)コンポーネントに基づいて再利用可能/カスタマイズ可能なコンポーネントを作成する必要があることです.さてここでなぜですかカスタムコンポーネントを作成すると、既定で提供するUIフレームワークを失うことはありません.
私は私の例をベースに2つの異なるユースケースを定義するつもりです.
  • 最初のユースケースは、例えば定義済み小道具を使用してVuetifyコンポーネントを使用することですv-text-field デフォルトでは、標準の色などでは、まだオーバーライドできるようにしたいoutlined or color デフォルトで定義されていてもプロパティは外側にあります.
  • 番目のユースケースは、Vuetifyコンポーネントに基づいてカスタムコンポーネントを構築しています.の組み合わせであるコンポーネントを構築するつもりですv-text-field and v-card . しかし、あなたはまだデフォルトの小道具/イベント/スロットを使用することができますv-text-field カスタムコンポーネントを手動で定義しないでください.
  • Vuetify(v 3.0タイタン)の次のバージョンでは、このケースは簡単にカバーされます.以下に例を示します.

    しかし、我々はまだこれらのものへの任意のアクセスを持っていない限り、我々はまだ現在のアプローチで立ち往生している.だからここでどのように最小限の努力でそれを行うことができます.

    ハウ


    今Vuetifyを使用しているときは、すべてのコンポーネントは、事前定義された小道具/イベント/スロットのいくつかの並べ替えがあります.上記の両方のユースケースをカバーするために、最も重要なことは、親コンポーネントからこれらの小道具/イベント/スロットを継承することです.では、どうやってそれを行うのですか?
    定義済み小道具を使用してカスタムテキストフィールドを作成する
    // CustomTextField.vue
    <template>
      <v-text-field
        :value="value"
        outlined
        color="primary"
        @input="v => $emit('input', v)" />
    </template>
    
    大丈夫、今では次のように使えます.
    // Parent.vue
    <custom-text-field v-model="value" />
    
    注意:v-model 砂糖構文:value="value" @input="v => $emit('input', v)"我々は、CustomTextFieldを作成してデフォルトでは、概説され、主要な色をしています.今、我々が使用したいならばflat or dense または他の支柱v-text-field オンcustom-text-field ? または何をオーバーライドする必要がある場合outlined and color プロパティはいくつかの点で、どのように我々はそれを行うつもりですか?基本的に我々はできないoutlined and color が静的に定義され、何も変更されません.
    したがって、現在これらの小道具を以下のようにカスタムコンポーネントに追加すると、現在動作しません
    <custom-text-field v-model="value" dense :outlined="false" />
    
    これは、親から送信されたすべての小道具を継承していないためです.

    小道具の継承


    親から小道具を受け継ぐために、我々は我々を助ける小さいトリックを使うことができます.VUEでは、各親コンポーネントは、それ自身を追加する属性を送信します.それらにアクセスするには$attrs このように親から送信された全てをバインドするためのテンプレートでは
    // CustomTextField.vue
    <template>
      <v-text-field
        v-bind="$attrs"
        :value="value"
        outlined
        color="primary"
        @input="v => $emit('input', v)" />
    </template>
    <script>
      export default {
        inheritAttrs: false
      }
    </script>
    
    とVoila!現在我々<v-text-field> CustomTextFieldコンポーネントは、<custom-text-field> . だから今すぐ簡単にすべての支柱を使用することができますv-text-field 提供する<custom-text-field> そして、このような内部の定義済みの小道具をオーバーライドします.
    // Parent.vue
    <custom-text-field v-model="value" dense :outlined="false" />
    

    重要


    $attrs Vue 2の間で振る舞う.Xと3.X!いくつかの違いがあります.
  • Vue 2で.X$attrs 含まれませんstyles and classes それは親から送られた
  • Vue 3で.X$attrs 含むstyles and classes それは親から送られた.Also $listeners 現在、内部に含まれます$attrs あとで話しましょう
  • 詳細についてはVue 3 docs.

    イベントの継承


    我々が我々が使っているように我々が我々のカスタムテキストフィールドを使うことができるように、我々は現在親から小道具を継承していますv-text-field 小道具で.それで、イベントについてどうですか?どのように我々は進行中のすべてのイベントを転送することができます<v-text-field> to <custom-text-field> ?
    解決策はここでも簡単です.
    // CustomTextField.vue
    <template>
      <v-text-field
        v-bind="$attrs"
        :value="value"
        outlined
        color="primary"
        v-on="$listeners"
        @input="v => $emit('input', v)" />
    </template>
    <script>
      export default {
        inheritAttrs: false
      }
    </script>
    
    我々はバインド$listeners with v-on そして、thats!だから今では簡単に任意のイベントを追加することができます<v-text-field> 提供する<custom-text-field> このように:
    // Parent.vue
    <custom-text-field
      v-model="value"
      dense
      :outlined="false"
      @blur="onFocus"
      @keypress="onKeypress" />
    

    重要


    リスナーはVue 3で削除されます.xと$ attrsの内部に含まれます.Vue 3を使用している場合.x、コンポーネントをバインドする$attrs バインドするのに十分でしょう$listeners , このように:
    // bind props, attrs, class, style in Vue 3.x
    <v-text-field v-bind="$attrs" />
    
    詳細についてはVue 3 docs.

    スロットの継承


    スロットは小道具やイベントより少し異なっています.これを行うには確かに異なる方法がありますが、ここで私は親から子に送られたすべてのスロットを転送するために何をしている.
    計算された中で親から送られたスロット名をすべて選びます.
    // CustomTextField.vue
    export default {
      setup(props, ctx) {
        const parentSlots = computed(() => Object.keys(ctx.slots))
    
        return { parentSlots }
      }  
    }
    
    その後<template> パートIスロットを通してループして、すべてのスロットを動的に宣言します.
    // CustomTextField.vue
    // Vue 2.x way, binding $listeners with v-on not needed in Vue 3.x
    <template>
      <v-text-field
        v-bind="$attrs"
        :value="value"
        outlined
        color="primary"
        v-on="$listeners"
        @input="v => $emit('input', v)"
      >
        <!-- Dynamically inherit slots from parent -->
        <template v-for="slot in parentSlots" #[slot]>
          <slot :name="slot" />
        </template>
      </v-text-field>
    
    </template>
    <script>
    export default {
      setup(props, ctx) {
        const parentSlots = computed(() => Object.keys(ctx.slots))
    
        return { parentSlots }
      }  
    }
    </script>
    
    なお、△は速記であるv-slot . ここでは、
    <template v-for="slot in parentSlots" #[slot]="props">
      <slot :name="slot" :props="props" />
    </template>
    
    スロットプロップを転送する.でもv-text-field コンポーネントは、任意の小道具を持っていないスロットをレンダリングしません.私は、これがVuetifyのバグであると思います.Issue here
    すごい!だから今ではフォワーディングv-text-field slots 親から子供へのスロットを使用することを意味<v-text-field> このように:
    // Parent.vue
    <custom-text-field
      v-model="value"
      dense
      :outlined="false"
      @blur="onFocus"
      @keypress="onKeypress"
    >
      <template #label>Custom Label</template>
      <template #message>Custom Message</template>
    </custom-text-field>
    

    ボーナス:小道具/イベント/スロットのカスタムメイドの使用


    我々は現在、継承で行われます.しかし、あなたのいくつかを使用する必要がある場合$attrs 別の要素に?たとえば、カスタムコンポーネントの中に<v-text-field> and <v-card> そして、あなたはcolor 両方のプロパティ.この時点で行くには別の方法があります.しかし、私が物事を組織し続けるのが好きである限り、私は1ポイントから組織/コントロールに計算を使います.
    例:
    // CustomTextField.vue
    // Vue 2.x way, binding $listeners with v-on not needed in Vue 3.x
    <template>
      <div>
        <v-text-field
          v-bind="[textFieldDefaults, $attrs]"
          :value="value"
          v-on="$listeners"
          @input="v => $emit('input', v)"
        >
          <template v-for="slot in parentSlots" #[slot]>
            <slot :name="slot" />
          </template>
        </v-text-field>
    
        <v-card v-bind="cardDefaults">
          <v-card-text>I am a card</v-card-text>
        </v-card>
    
      </div>
    </template>
    <script>
    export default {
      setup(props, ctx) {
        const parentSlots = computed(() => Object.keys(ctx.slots))
    
        const textFieldDefaults = computed(() => ({
          outlined: true,
          dense: true,
          color: 'primary'
        }))
    
        const cardDefaults = computed(() => ({
          color: ctx.attrs.color || 'primary'
        }))
    
        return { parentSlots, textFieldDefaults, cardDefaults }
      }  
    }
    </script>
    
    ではここで何が起きているのか.我々は2つの計算変数を作成しているv-text-field デフォルトとv-card .
  • インtextFieldDefaults デフォルトのテキストフィールドの小道具を定義し、v-bind=[textFieldDefaults, $attrs] . デフォルト値のいずれかが上位から送信された場合、値$attrs デフォルトの小道具を上書きする.
  • インcardDefaults 私たちだけを取るcolor プロパティ.あなたが望むならば、あなたは簡単にここから親から送られた他の支柱または聞き手を加えることができました.
  • 両方textFieldDefaults and cardDefaults それらを反応させて、親で起こっている変化を聞くことができるように、計算されて、宣言されなければなりません.
  • 結論


    要約すると、Vueは私たちが行う必要がある達成するために多くの異なるオプションを提供しています.フレームワークが既に提供しているものを失うことなく、UIフレームワークに基づいたカスタムコンポーネントを作成することは非常に簡単です.確かにいくつかのエッジケースがあるかもしれないが、私は上記の説明しようとしたアプローチでは、それらのほとんどを解決することができますと思う.うまくいけば、このポストは、コンセプトを理解するのに役立ちました!
    読んでくれてありがとう!