1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

スロット持ちコンポーネントをラッピングする際にスロットを継承したい(ElementPlusでel-inputをラッピングする際のprependとappendのラッピングでちょっとハマった話)

Last updated at Posted at 2024-03-29

はじめに

通常のel-inputは以下のように使いますが、

input.vue
<el-input>
  <template #prepend></template>
  <template #append></template>
</el-input>

(↓のようなUIになります)

image.png

入力時に値を整形するようにラッピングコンポーネントを作ろうとしたんですが、prependとappendを再利用する方法に詰まったので、解決方法を備忘録として残しておきます。

NG例:雑にラッピングしてみた

ng-wrapper.vue
<template>
  <el-input v-model="innerValue" @input="oninput">
    <template #prepend>
      <slot name="prepend" />
    </template>
    <template #append>
      <slot name="append" />
    </template>
  </el-input>
</template>

これでも、以下のようにラッピングコンポーネントにprepend・append指定したら表示されます。

sample.vue
<Wrapper>
  <template #prepend></template>
  <template #append></template>
</Wrapper>

ただ、以下のように<template #prepend>``<template #append>を指定してなくても、el-inputのprependとappendが表示されてしまいます。

sample.vue
<Wrapper>
</Wrapper>

そもそもElementPlus側がどうやって実現してんのかな~?って疑問になったので、el-inputのコードを見てみました。

packages/components/input/src/input.vue
<template>
  <div
    v-bind="containerAttrs"
    :class="containerKls"
    :style="containerStyle"
    :role="containerRole"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
  >
    <!-- input -->
    <template v-if="type !== 'textarea'">
      <!-- prepend slot -->
      <div v-if="$slots.prepend" :class="nsInput.be('group', 'prepend')">
        <slot name="prepend" />
      </div>

引用:https://github.com/element-plus/element-plus/blob/dev/packages/components/input/src/input.vue

なにやら<div v-if="$slots.prepend"<slot name="prepend" />の表示制御している様子。
$slotsについては公式にも記載がありました。
https://ja.vuejs.org/api/component-instance.html#slots

解決

スロットプラグイン($slots)を使うことで解決できました。

wrapper.vue
<template>
  <el-input v-model="innerValue" @input="oninput">
    <!-- ラッパーコンポーネントのprependが指定されていればel-inputのprependを表示 -->
    <template v-if="$slots.prepend" #prepend>
      <slot name="prepend" />
    </template>
    <!-- ラッパーコンポーネントのappendが指定されていればel-inputのappendを表示 -->
    <template v-if="$slots.append" #append>
      <slot name="append" />
    </template>
  </el-input>
</template>
<scirpt lang="ts" setup>
import { ref, watch } from 'vue';

// modelValueはv-modelで指定した値
const props = defineProps<{ modelValue: number; }>();
// v-modelで指定された値をこのコンポーネントから更新するためのイベント
const emits = defineEmits(['update:modelValue']);

// コンポーネント内部用の変数
const innerValue = ref();

// 呼び元での値の更新を反映
watch(() => props.modelValue, (v) => innerValue.value = cnv(v));

const cnv = (value) => {
  // 数値以外を削除
  return value.replace(/[^0-9]/g, '')
}

const oninput = () => {
  // ここで入力値を整形したりする
  const value = cnv(innerValue.value);
  // 入力されたら呼び元の値を更新するためのイベント発火
  emits('update:modelValue', value);
}
</setup>

$slots.prepend$slots.appendで以下のようにラッピングコンポーネント呼び出しにて、<template #prepend> <template #append>が指定されているか判断できます。

sample.vue
<Wrapper>
  <!-- これを指定すると、$slots.prependにインスタンスが設定される -->
  <template #prepend></template>
  <!-- これを指定すると、$slots.appendにインスタンスが設定される -->
  <template #append></template>
</Wrapper>
1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?