Membuat Komponen Input Kustom dengan Dukungan v-model di Vue 3

Dalam pengembangan aplikasi menggunakan Vue 3, sering kali kita membutuhkan komponen input kustom yang dapat digunakan berulang kali di berbagai tempat. Salah satu fitur yang membuat Vue fleksibel adalah dukungan terhadap v-model, yaitu mekanisme dua arah (two-way data binding) yang memudahkan sinkronisasi data antara komponen induk (parent) dan anak (child).

Namun, ketika membuat komponen sendiri, kita tidak bisa langsung menggunakan v-model tanpa menambahkan logika tertentu. Artikel ini akan membahas secara detail cara membuat komponen input kustom di Vue 3 dengan dukungan penuh untuk v-model, dilengkapi dengan beberapa contoh implementasi.


1. Memahami Cara Kerja v-model di Vue 3

Di Vue 2, v-model biasanya bekerja dengan value sebagai prop dan input sebagai event yang dipancarkan. Namun di Vue 3, v-model telah disederhanakan dan lebih fleksibel:

  • v-model akan secara default mengikat ke modelValue (bukan value lagi).
  • Setiap perubahan nilai harus dipancarkan (emit) menggunakan event update:modelValue.

Contoh singkat pada komponen bawaan:

<input v-model="username" />

Di balik layar, Vue akan melakukan binding seperti ini:

<input
  :value="username"
  @input="username = $event.target.value"
/>

2. Membuat Komponen Input Kustom Dasar

Mari kita buat komponen sederhana bernama CustomInput.vue. Komponen ini akan menerima modelValue sebagai prop, lalu memancarkan event update:modelValue setiap kali pengguna mengetik sesuatu.

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
    class="custom-input"
  />
</template>

<script setup>
defineProps({
  modelValue: String
})

defineEmits(['update:modelValue'])
</script>

<style scoped>
.custom-input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 6px;
}
</style>

Penggunaan di parent:

<template>
  <CustomInput v-model="name" />
  <p>Nama: {{ name }}</p>
</template>

<script setup>
import CustomInput from './CustomInput.vue'
import { ref } from 'vue'

const name = ref('')
</script>

Kini, setiap kali kita mengetik, nilai di parent (name) akan otomatis berubah mengikuti input.


3. Menambahkan Props Tambahan

Komponen kustom tidak harus terbatas hanya menerima modelValue. Misalnya, kita bisa menambahkan label, placeholder, atau bahkan tipe input (text, password, email, dll.) agar lebih fleksibel.

<template>
  <label>
    {{ label }}
    <input
      :type="type"
      :placeholder="placeholder"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      class="custom-input"
    />
  </label>
</template>

<script setup>
defineProps({
  modelValue: String,
  label: { type: String, default: '' },
  placeholder: { type: String, default: '' },
  type: { type: String, default: 'text' }
})

defineEmits(['update:modelValue'])
</script>

Penggunaan di parent:

<CustomInput
  v-model="email"
  label="Alamat Email"
  placeholder="Masukkan email anda"
  type="email"
/>

4. Mendukung Beberapa v-model (Multiple Binding)

Vue 3 mendukung lebih dari satu v-model pada satu komponen. Ini berguna untuk kasus di mana kita ingin mengelola lebih dari satu nilai. Misalnya, membuat input password dengan toggle visibilitas.

<template>
  <div>
    <input
      :type="visible ? 'text' : 'password'"
      :value="password"
      @input="$emit('update:password', $event.target.value)"
    />
    <button type="button" @click="$emit('update:visible', !visible)">
      {{ visible ? 'Sembunyikan' : 'Tampilkan' }}
    </button>
  </div>
</template>

<script setup>
defineProps({
  password: String,
  visible: Boolean
})

defineEmits(['update:password', 'update:visible'])
</script>

Penggunaan:

<PasswordInput v-model:password="userPassword" v-model:visible="isPasswordVisible" />

5. Validasi dan Pesan Error

Dalam banyak kasus, kita juga ingin agar input kustom mendukung validasi. Misalnya, kita dapat menampilkan pesan error jika input tidak sesuai aturan.

<template>
  <div>
    <label>
      {{ label }}
      <input
        :value="modelValue"
        @input="$emit('update:modelValue', $event.target.value)"
        :class="{ 'has-error': error }"
      />
    </label>
    <p v-if="error" class="error-text">{{ error }}</p>
  </div>
</template>

<script setup>
defineProps({
  modelValue: String,
  label: String,
  error: String
})

defineEmits(['update:modelValue'])
</script>

<style scoped>
.has-error {
  border-color: red;
}
.error-text {
  color: red;
  font-size: 12px;
}
</style>

6. Menjadikan Komponen Lebih Reusable

Dengan menambahkan props, event, dan styling, kita bisa membuat komponen input kustom yang reusable untuk berbagai kebutuhan. Seiring pertumbuhan aplikasi, kita bahkan bisa membuat library komponen internal berisi berbagai input kustom, seperti:

  • TextInput
  • PasswordInput
  • NumberInput dengan format angka
  • DatePicker kustom
  • Dropdown dengan v-model

Membuat komponen input kustom di Vue 3 dengan dukungan v-model bukan hanya memudahkan pengelolaan data, tetapi juga membuat kode lebih rapi dan mudah digunakan kembali. Dengan memahami bahwa v-model bekerja melalui modelValue dan update:modelValue, kita bisa mengembangkan berbagai variasi komponen input sesuai kebutuhan, baik sederhana maupun kompleks.

Mulailah dari komponen kecil seperti CustomInput, lalu kembangkan menjadi library internal agar seluruh tim dapat menggunakannya dengan konsisten di seluruh aplikasi.