Quando crei componenti per il tuo progetto, tutto inizia divertente e facile. Aggiungi un po’ di stile e ecco. MyButton.vue <template> <button class="my-fancy-style"> <slot></slot> </button> </template> Poi ti rendi subito conto che hai bisogno di una dozzina di props, perché il tuo team di progettazione vuole che siano di diversi colori e dimensioni, con icone a sinistra e a destra, con contatori... 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 }); Dopo tutto, non puoi avere i pulsanti “Cancel” e “Ok” dello stesso colore, e hai bisogno di loro per reagire alle interazioni degli utenti. 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 }); Bene, hai l'idea: ci sarà qualcosa di selvaggio, come passare o aggiungendo l'autofocus - tutti sappiamo come sembra semplice in Figma fino a quando la vita reale colpisce duramente. width: 100% Ora immaginate un pulsante di collegamento: sembra lo stesso, ma quando lo premete, dovreste andare al link esterno o interno. o tag ogni volta, ma per favore non. Puoi anche aggiungere e i vantaggi per la tua componente iniziale, ma ti sentirai soffocato abbastanza presto: <RouterLink> <a> to href <component :is="to ? RouterLink : href ? 'a' : 'button'" <!-- ugh! --> Naturalmente, avrai bisogno di un componente di "secondo livello" che avvolga il tuo pulsante (trattare anche i contorni di collegamento ipertestuale predefiniti e alcune altre cose interessanti, ma li ometterò per semplicità): <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> Ed è qui che inizia la nostra storia. Square One Piazza Uno Bene, fondamentalmente funziona, non mentirò. puoi ancora digitare Ma non ci sarà alcun autocomplete per i props derivati, che non è cool: <MyLinkButton :counter=“2"> Possiamo propagare i props in silenzio, ma l’IDE non sa nulla di loro, e questo è un peccato. La soluzione semplice e ovvia è propagarla esplicitamente: <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> L'IDE avrà l'autocomplete appropriato. Avremo un sacco di dolore e rimpianti di sostenerlo. Ovviamente, il principio "Non ripetere te stesso" non è stato applicato qui, il che significa che dovremo sincronizzare ogni aggiornamento. Un giorno, dovrai aggiungere un altro prop, e dovrai trovare ogni componente che avvolge il componente di base. Sì, Button e LinkButton sono probabilmente sufficienti, ma immaginate TextInput e una dozzina di componenti che dipendono da esso: PasswordInput, EmailInput, NumberInput, DateInput, HellKnowsWhatElseInput. Dopo tutto, è brutta. E più props abbiamo, più brutta diventa. Clean It Up pulire in alto È abbastanza difficile riutilizzare un tipo anonimo, quindi diamo un nome. // MyButton.props.ts export interface MyButtonProps { theme?: ComponentTheme; small?: boolean; icon?: IconSvg; rightIcon?: IconSvg; counter?: number; disabled?: boolean; loading?: boolean; } Non è possibile esportare un'interfaccia dal file a causa di alcuni interni magia, quindi dobbiamo creare una Sul lato luminoso, guarda cosa abbiamo qui: .vue script setup .ts const props = withDefaults(defineProps<MyButtonProps>(), { theme: ComponentTheme.BLUE, icon: undefined, rightIcon: undefined, counter: undefined, }); Molto più pulito, non è vero?Ecco quello ereditato: interface MyLinkButtonProps { to?: RouteLocationRaw; href?: string; } const props = defineProps<MyButtonProps & MyLinkButtonProps>(); Tuttavia, ecco un problema: ora, quando i props di base sono trattati come “I profili, non sono propagati con Ancora, quindi dobbiamo farlo noi stessi. 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> Tutto bene, ma passiamo un po' di più di quanto vogliamo: Come potete vedere, ora il nostro pulsante sottostante ha anche un Non è una tragedia, solo un po 'di confusione e byte extra, anche se non cool. 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> Ora, passiamo solo ciò che deve essere passato, ma tutte quelle righe letterali non sembrano fantastiche, no? Interfaces vs Abstract Interfaces Interfacce versus interfacce astratte Se hai mai lavorato con linguaggi orientati agli oggetti, probabilmente conosci cose come: Sfortunatamente, in TypeScript, le interfacce sono effimere; non esistono al tempo di esecuzione, e non possiamo facilmente scoprire quali campi appartengono alla . riflessione MyButtonProps Ciò significa che abbiamo due opzioni.In primo luogo, possiamo mantenere le cose come sono: ogni volta che aggiungiamo un Bisogna anche escludere da (e anche se ci dimentichiamo di questo, non è una grande cosa). MyLinkButton propsToPass Il secondo modo è quello di usare oggetti invece di interfacce. Può sembrare insensato, ma lascia che io codifichi qualcosa: non sarà orribile; prometto. Il terribile. Questo // MyButton.props.ts export const defaultMyButtonProps: MyButtonProps = { theme: ComponentTheme.BLUE, small: false, icon: undefined, rightIcon: undefined, counter: undefined, disabled: false, loading: false, }; Non ha senso creare un oggetto solo per creare un oggetto, ma possiamo usarlo per i props predefiniti. diventa ancora più pulito: MyButton.vue const props = withDefaults(defineProps<MyButtonProps>(), defaultMyButtonProps); Ora abbiamo solo bisogno di aggiornare in : propsToPass MyLinkButton.vue const propsToPass = computed(() => Object.fromEntries( Object.entries(props).filter(([key, _]) => Object.hasOwn(defaultMyButtonProps, key) ) ) ); Per fare questo lavoro, dobbiamo definire esplicitamente tutti i e I campi in Altrimenti, l’oggetto non “haOwn”. undefined null defaultMyButtonProps In questo modo, ogni volta che si aggiunge un prop al componente di base, sarà anche necessario aggiungerlo all'oggetto con i valori predefiniti. Così, sì, è di nuovo in due luoghi, e forse non è migliore della soluzione del capitolo precedente. I’m Done Sono fatto Non è un capolavoro, ma è probabilmente il meglio che possiamo fare entro i limiti di TypeScript. Sembra anche che avere i tipi di prop all'interno del file SFC sia meglio, ma non posso dire che spostarli in un file separato lo abbia reso molto peggio.Ma ha sicuramente reso il riutilizzo del prop migliore, quindi lo considererò una piccola vittoria in una battaglia infinita che chiamiamo lavoro. Puoi trovare il codice di questo articolo su GitHub. di Github