Ketika Anda membuat komponen untuk proyek Anda, semuanya dimulai dengan menyenangkan dan mudah. Tambahkan sedikit styling, dan inilah. MyButton.vue <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 melewati atau menambahkan autofocus - kita semua tahu bagaimana terlihat sederhana di Figma sampai kehidupan nyata memukul keras. width: 100% Sekarang bayangkan tombol tautan: terlihat sama, tetapi ketika Anda menekannya, Anda harus pergi ke tautan eksternal atau internal. atau tag setiap kali, tapi tolong jangan. Anda juga dapat menambahkan dan props ke komponen awal Anda, tetapi Anda akan merasa tersedak cukup cepat: <RouterLink> <a> to href <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 Tapi tidak akan ada autocomplete untuk props yang berasal, yang tidak keren: <MyLinkButton :counter=“2"> 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 Berdasarkan beberapa faktor internal keajaiban, maka kita perlu membuat Di sisi yang cerah, lihat apa yang kami dapatkan di sini: .vue script setup .ts 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 sebagai ‘s props, mereka tidak disebarkan dengan lebih, jadi kita harus melakukannya sendiri. MyLinkButton v-bind=”$attrs” <!-- 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: Seperti yang Anda lihat, sekarang tombol bawah kami juga memiliki Ini bukan tragedi, hanya sedikit kekacauan dan byte ekstra, meskipun tidak keren. href <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 seperti Sayangnya, di TypeScript, antarmuka efemeral; mereka tidak ada pada runtime, dan kita tidak dapat dengan mudah mencari tahu bidang mana yang termasuk dalam . Refleksi MyButtonProps Ini berarti bahwa kita memiliki dua pilihan. pertama, kita dapat menjaga hal-hal seperti itu: setiap kali kita menambahkan prop Kita juga harus mengecualikan dari (Dan bahkan jika kita melupakannya, itu bukan masalah besar). MyLinkButton propsToPass Cara kedua adalah menggunakan objek alih-alih antarmuka. mungkin terdengar tidak masuk akal, tetapi biarkan saya mengkode sesuatu: itu tidak akan mengerikan; saya berjanji. yang mengerikan. yang // 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. menjadi lebih bersih: MyButton.vue const props = withDefaults(defineProps<MyButtonProps>(), defaultMyButtonProps); Sekarang, kita hanya perlu memperbarui dalam : propsToPass MyLinkButton.vue const propsToPass = computed(() => Object.fromEntries( Object.entries(props).filter(([key, _]) => Object.hasOwn(defaultMyButtonProps, key) ) ) ); Untuk melakukan pekerjaan ini, kita perlu secara eksplisit menentukan semua dan Bidang di Jika tidak, maka objek tidak akan “memiliki”. undefined null defaultMyButtonProps 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