Vue.js: Propagasi alat peraga seperti pro

Saat Anda membuat komponen untuk proyek Anda, semuanya mulai menyenangkan dan mudah. Membuat MyButton.vuetambahkan beberapa gaya, dan voila.


Maka Anda segera menyadari bahwa Anda memerlukan selusin alat peraga, karena tim desain Anda menginginkannya dengan warna dan ukuran yang berbeda, dengan ikon di kiri dan kanan, dengan counter …

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

Masih oke; itu masuk akal. Lagi pula, Anda tidak dapat memiliki tombol “batal” dan “OK” dengan warna yang sama, dan Anda membutuhkannya untuk bereaksi terhadap interaksi pengguna. Oh, benar, interaksi.

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

Nah, Anda mendapatkan idenya: akan ada sesuatu yang liar, seperti passing width: 100% Atau menambahkan fokus otomatis – kita semua tahu bagaimana tampilannya sederhana di Figma sampai kehidupan nyata menyerang.

Sekarang bayangkan tombol tautan: terlihat sama, tetapi ketika Anda menekannya, Anda harus pergi ke tautan eksternal atau internal. Anda bisa membungkusnya atau Tag setiap kali, tapi tolong jangan. Anda juga dapat menambahkan to Dan href Alat peraga untuk komponen awal Anda, tetapi Anda akan segera merasa tercekik:


Tentu saja, Anda membutuhkan komponen “tingkat kedua” yang membungkus tombol Anda (itu juga akan menangani garis besar hyperlink default dan beberapa hal menarik lainnya, tetapi saya akan menghilangkannya demi kesederhanaan):




Dan di sinilah kisah kita dimulai.

Persegi satu

Nah, pada dasarnya itu akan berhasil, saya tidak akan berbohong. Anda masih bisa mengetik dan itu akan baik -baik saja. Tetapi tidak akan ada pelengkapan otomatis untuk alat peraga yang diturunkan, yang tidak keren:

Hanya Hanya

Kita dapat menyebarkan alat peraga secara diam -diam, tetapi IDE tidak tahu apa -apa tentang mereka, dan itu memalukan.

Solusi langsung dan jelas adalah menyebarkannya secara eksplisit:




Itu akan berhasil. IDE akan memiliki pelengkapan otomatis yang tepat. Kami akan memiliki banyak rasa sakit dan penyesalan mendukungnya.

Jelas, prinsip “jangan ulangi diri Anda” tidak diterapkan di sini, yang berarti bahwa kita perlu menyinkronkan setiap pembaruan. Suatu hari, Anda harus menambahkan prop lain, dan Anda harus menemukan setiap komponen yang membungkus yang dasar. Ya, Button dan LinkButton mungkin cukup, tetapi bayangkan TextInput dan selusin komponen yang bergantung padanya: kata sandi, emailInput, numberInput, DateInput, HellknowHatelSeInput. Menambahkan alat peraga seharusnya tidak menyebabkan penderitaan.

Lagipula, itu jelek. Dan semakin banyak alat peraga yang kita miliki, semakin jelek.

Bersihkan

Cukup sulit untuk menggunakan kembali tipe anonim, jadi mari kita beri nama.

// 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 .vue file karena beberapa internal script setup sihir, jadi kita perlu membuat terpisah .ts mengajukan. Sisi baiknya, lihat apa yang kita miliki di sini:

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

Jauh lebih bersih, bukan? Dan inilah yang diwariskan:

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

const props = defineProps();

Namun, inilah masalahnya: Sekarang, saat alat peraga dasar diperlakukan sebagai MyLinkButtonAlat peraga, mereka tidak diperbanyak dengan v-bind=”$attrs” lagi, jadi kita perlu melakukannya sendiri.




   
    
  

Semuanya baik, tapi kami melewati sedikit lebih dari yang kami inginkan:

W3C tidak setujuW3C tidak setuju

Seperti yang Anda lihat, sekarang, tombol mendasar kami juga memiliki a href atribut. Ini bukan tragedi, hanya sedikit kekacauan dan byte ekstra, meskipun tidak keren. Mari kita bersihkan juga.




Sekarang, kita hanya melewati apa yang harus dilewati, tetapi semua literal string itu tidak terlihat luar biasa, bukan? Dan itulah kisah naskah paling menyedihkan, teman -teman. Biarkan saya memberi tahu Anda tentang itu.

Antarmuka vs antarmuka abstrak

Jika Anda pernah bekerja dengan bahasa yang berorientasi objek yang tepat, Anda mungkin tahu tentang hal-hal seperti refleksi, yang memungkinkan kami untuk mendapatkan metadata tentang struktur kami. Sayangnya, dalam naskah, antarmuka adalah fana; Mereka tidak ada saat runtime, dan kami tidak dapat dengan mudah mengetahui bidang mana yang menjadi milik MyButtonProps.

Itu berarti bahwa kami memiliki dua opsi. Pertama, kita dapat menjaga hal -hal seperti: setiap kali kita menambahkan prop MyLinkButtonkita juga perlu mengecualikannya propsToPass (Dan bahkan jika kita melupakannya, itu bukan masalah besar).

Cara kedua adalah dengan menggunakan objek, bukan antarmuka. Ini mungkin terdengar tidak masuk akal, tapi izinkan saya membuat kode sesuatu: itu tidak akan mengerikan; Saya berjanji. Yah, setidaknya tidak itu 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 objek, tetapi kita dapat menggunakannya untuk alat peraga default. Deklarasi di MyButton.vue menjadi lebih bersih:

const props = withDefaults(defineProps(), defaultMyButtonProps);

Sekarang, kita hanya perlu memperbarui propsToPass di dalam MyLinkButton.vue:

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

Untuk membuat ini berhasil, kita perlu secara eksplisit mendefinisikan semua undefined Dan null bidang di defaultMyButtonProps; Kalau tidak, objek itu tidak “memiliki”.

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

Saya sudah selesai

Ini bukan karya agung, tapi mungkin yang terbaik yang bisa kita lakukan dalam keterbatasan TypeScript.

Tampaknya juga memiliki jenis prop di dalam file SFC lebih baik, tetapi saya tidak dapat mengatakan bahwa memindahkannya ke file terpisah membuatnya jauh lebih buruk. Tapi itu pasti membuat penyangga kembali lebih baik, jadi saya akan menganggapnya sebagai kemenangan kecil dalam pertempuran tanpa akhir yang kita sebut pekerjaan.

Anda dapat menemukan kode dari artikel ini di GitHub.