Sejarah baru

Vue.js: Mengembangkan Props Seperti Pro

oleh Andrei Sieedugin7m2025/05/03
Read on Terminal Reader

Terlalu panjang; Untuk membaca

Ketika Anda membuat komponen untuk proyek Anda, semuanya mulai menyenangkan dan mudah. Buat 'MyButton.vue' dan tambahkan beberapa gaya, dan voilà. Kemudian Anda segera menyadari bahwa Anda membutuhkan selusin props, karena tim desain Anda ingin itu memiliki warna dan ukuran yang berbeda, dengan ikon di kiri dan kanan, dengan penghitung. Setelah semua, Anda tidak dapat memiliki tombol "Batal" dan "Ok" dari warna yang sama, dan Anda membutuhkannya untuk bereaksi terhadap interaksi pengguna.
featured image - Vue.js: Mengembangkan Props Seperti Pro
Andrei Sieedugin HackerNoon profile picture

Ketika Anda membuat komponen untuk proyek Anda, semuanya dimulai dengan menyenangkan dan mudah.MyButton.vueTambahkan sedikit styling, dan inilah.

<template>
  <button class="my-fancy-style">
    <slot></slot>
  </button>
</template>


Kemudian Anda segera menyadari bahwa Anda membutuhkan selusin props, karena tim desain Anda ingin itu memiliki warna dan ukuran yang berbeda, dengan ikon di kiri dan kanan, dengan counter...

const props = withDefaults(defineProps<{
  theme?: ComponentTheme;
  small?: boolean;
  icon?: IconSvg; // I’ve described how I cook icons in my previous article
  rightIcon?: IconSvg;
  counter?: number;
}>(), {
  theme: ComponentTheme.BLUE,
  icon: undefined,
  rightIcon: undefined,
  counter: undefined
});


Setelah semua, Anda tidak dapat memiliki tombol “Batalkan” dan “Ok” warna yang sama, dan Anda membutuhkannya untuk bereaksi terhadap interaksi pengguna.

const props = withDefaults(defineProps<{
  theme?: ComponentTheme;
  small?: boolean;
  icon?: IconSvg;
  rightIcon?: IconSvg;
  counter?: number;
  disabled?: boolean;
  loading?: boolean;
}>(), {
  theme: ComponentTheme.BLUE,
  icon: undefined,
  rightIcon: undefined,
  counter: undefined
});


Nah, Anda mendapatkan ide: akan ada sesuatu yang liar, seperti melewatiwidth: 100%atau menambahkan autofocus - kita semua tahu bagaimana terlihat sederhana di Figma sampai kehidupan nyata memukul keras.


Sekarang bayangkan tombol tautan: terlihat sama, tetapi ketika Anda menekannya, Anda harus pergi ke tautan eksternal atau internal.<RouterLink>atau<a>tag setiap kali, tapi tolong jangan. Anda juga dapat menambahkantodanhrefprops ke komponen awal Anda, tetapi Anda akan merasa tersedak cukup cepat:

<component
  :is="to ? RouterLink : href ? 'a' : 'button'"
  <!-- ugh! -->


Tentu saja, Anda akan membutuhkan komponen "tingkat kedua" yang membungkus tombol Anda (itu juga akan menangani ikhtisar hyperlink default dan beberapa hal menarik lainnya, tetapi saya akan melewatkan mereka demi kesederhanaan):

<template>
  <component
    :is="props.to ? RouterLink : 'a'"
    :to="props.to"
    :href="props.href"
    class="my-link-button"
  >
    <MyButton v-bind="$attrs">
      <slot></slot>
    </MyButton>
  </component>
</template>

<script lang="ts" setup>
import MyButton from './MyButton.vue';
import { RouteLocationRaw, RouterLink } from 'vue-router';

const props = defineProps<{
  to?: RouteLocationRaw;
  href?: string;
}>();
</script>

Di sinilah kisah kita dimulai.

Square One

Pantai Satu

Nah, pada dasarnya itu akan bekerja, saya tidak akan berbohong. Anda masih bisa mengetik<MyLinkButton :counter=“2">Tapi tidak akan ada autocomplete untuk props yang berasal, yang tidak keren:


Only "href" and "to"


Kita dapat menyebarkan props diam-diam, tetapi IDE tidak tahu apa-apa tentang mereka, dan itu malu.


Solusi yang sederhana dan jelas adalah menyebarkannya secara eksplisit:

<template>
  <component
    :is="props.to ? RouterLink : 'a'"
    :to="props.to"
    :href="props.href"
    class="my-link-button"
  >
    <MyButton
      :theme="props.theme"
      :small="props.small"
      :icon="props.icon"
      :right-icon="props.rightIcon"
      :counter="props.counter"
      :disabled="props.disabled"
      :loading="props.loading"
    >
      <slot></slot>
    </MyButton>
  </component>
</template>

<script lang="ts" setup>
// imports...

const props = withDefaults(
  defineProps<{
    theme?: ComponentTheme;
    small?: boolean;
    icon?: IconSvg;
    rightIcon?: IconSvg;
    counter?: number;
    disabled?: boolean;
    loading?: boolean;

    to?: RouteLocationRaw;
    href?: string;
  }>(),
  {
    theme: ComponentTheme.BLUE,
    icon: undefined,
    rightIcon: undefined,
    counter: undefined,
  }
);
</script>


Ini akan bekerja. IDE akan memiliki autocomplete yang tepat. Kami akan memiliki banyak rasa sakit dan menyesal mendukungnya.


Jelas, prinsip “Jangan Ulangi Diri Sendiri” tidak diterapkan di sini, yang berarti bahwa kita akan perlu menyinkronkan setiap pembaruan. Suatu hari, Anda akan perlu menambahkan prop lain, dan Anda harus menemukan setiap komponen yang membungkus komponen dasar. Ya, Button dan LinkButton mungkin cukup, tetapi bayangkan TextInput dan selusin komponen yang bergantung padanya: PasswordInput, EmailInput, NumberInput, DateInput, HellKnowsWhatElseInput. Menambahkan prop tidak harus menyebabkan penderitaan.


Setelah semua, itu jelek. dan semakin banyak props yang kita miliki, semakin jelek itu menjadi.

Clean It Up

Bersihkan ke atas

Sangat sulit untuk menggunakan kembali jenis anonim, jadi mari kita berikan namanya.

// MyButton.props.ts

export interface MyButtonProps {
  theme?: ComponentTheme;
  small?: boolean;
  icon?: IconSvg;
  rightIcon?: IconSvg;
  counter?: number;
  disabled?: boolean;
  loading?: boolean;
}


Kami tidak dapat mengekspor antarmuka dari.vueBerdasarkan beberapa faktor internalscript setupkeajaiban, maka kita perlu membuat.tsDi sisi yang cerah, lihat apa yang kami dapatkan di sini:

const props = withDefaults(defineProps<MyButtonProps>(), {
  theme: ComponentTheme.BLUE,
  icon: undefined,
  rightIcon: undefined,
  counter: undefined,
});


Terlalu bersih, bukan? dan inilah yang diwariskan:

interface MyLinkButtonProps {
  to?: RouteLocationRaw;
  href?: string;
}

const props = defineProps<MyButtonProps & MyLinkButtonProps>();


Namun, di sini ada masalah: sekarang, ketika props dasar diperlakukan sebagaiMyLinkButton‘s props, mereka tidak disebarkan denganv-bind=”$attrs”lebih, jadi kita harus melakukannya sendiri.

<!-- MyLinkButton.vue -->

<component
  :is="props.to ? RouterLink : 'a'"
  :to="props.to"
  :href="props.href"
  class="my-link-button"
>
  <MyButton v-bind="props"> <!-- there we go -->
    <slot></slot>
  </MyButton>
</component>


Itu semua baik-baik saja, tetapi kita melewatkan sedikit lebih dari yang kita inginkan:


W3C disapproves


Seperti yang Anda lihat, sekarang tombol bawah kami juga memilikihrefIni bukan tragedi, hanya sedikit kekacauan dan byte ekstra, meskipun tidak keren.

<template>
  <component
    :is="props.to ? RouterLink : 'a'"
    :to="props.to"
    :href="props.href"
    class="my-link-button"
  >
    <MyButton v-bind="propsToPass">
      <slot></slot>
    </MyButton>
  </component>
</template>

<script lang="ts" setup>
// imports and definitions…

const props = defineProps<MyButtonProps & MyLinkButtonProps>();

const propsToPass = computed(() =>
  Object.fromEntries(
    Object.entries(props).filter(([key, _]) => !["to", "href"].includes(key))
  )
);
</script>


Sekarang, kita hanya melewati apa yang harus diturunkan, tetapi semua huruf string itu tidak terlihat hebat, apakah mereka? dan itu adalah cerita TypeScript yang paling sedih, guys.

Interfaces vs Abstract Interfaces

Interface vs Interface abstrak

Jika Anda pernah bekerja dengan bahasa berorientasi objek yang tepat, Anda mungkin tahu tentang hal-hal sepertiRefleksiSayangnya, di TypeScript, antarmuka efemeral; mereka tidak ada pada runtime, dan kita tidak dapat dengan mudah mencari tahu bidang mana yang termasuk dalamMyButtonProps.


Ini berarti bahwa kita memiliki dua pilihan. pertama, kita dapat menjaga hal-hal seperti itu: setiap kali kita menambahkan propMyLinkButtonKita juga harus mengecualikan daripropsToPass(Dan bahkan jika kita melupakannya, itu bukan masalah besar).


Cara kedua adalah menggunakan objek alih-alih antarmuka. mungkin terdengar tidak masuk akal, tetapi biarkan saya mengkode sesuatu: itu tidak akan mengerikan; saya berjanji.yangyang mengerikan.

// MyButton.props.ts

export const defaultMyButtonProps: MyButtonProps = {
  theme: ComponentTheme.BLUE,
  small: false,
  icon: undefined,
  rightIcon: undefined,
  counter: undefined,
  disabled: false,
  loading: false,
};


Tidak masuk akal untuk membuat objek hanya untuk membuat sebuah objek, tetapi kita dapat menggunakannya untuk props default.MyButton.vuemenjadi lebih bersih:

const props = withDefaults(defineProps<MyButtonProps>(), defaultMyButtonProps);


Sekarang, kita hanya perlu memperbaruipropsToPassdalamMyLinkButton.vue:

const propsToPass = computed(() =>
  Object.fromEntries(
    Object.entries(props).filter(([key, _]) =>
      Object.hasOwn(defaultMyButtonProps, key)
    )
  )
);


Untuk melakukan pekerjaan ini, kita perlu secara eksplisit menentukan semuaundefineddannullBidang didefaultMyButtonPropsJika tidak, maka objek tidak akan “memiliki”.


Dengan cara ini, setiap kali Anda menambahkan prop ke komponen dasar, Anda juga harus menambahkannya ke objek dengan nilai default. Jadi, ya, itu dua tempat lagi, dan mungkin tidak lebih baik daripada solusi dari bab sebelumnya.

I’m Done

Aku sudah selesai

Ini bukan karya utama, tetapi itu mungkin yang terbaik yang dapat kita lakukan dalam keterbatasan TypeScript.


Tampaknya juga bahwa memiliki jenis prop di dalam file SFC lebih baik, tetapi saya tidak bisa mengatakan bahwa memindahkan mereka ke file terpisah membuatnya jauh lebih buruk.


Anda dapat menemukan kode dari artikel ini di GitHub.

GitHub

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks
OSZAR »