Кога ќе креирате компоненти за вашиот проект, сè започнува забавно и лесно. Додадете малку стил и еве го тоа. MyButton.vue <template> <button class="my-fancy-style"> <slot></slot> </button> </template> Тогаш веднаш сфаќате дека ви треба десетина приклучоци, бидејќи вашиот дизајнерски тим сака да биде во различни бои и големини, со икони на лево и десно, со бројачи... 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 }); На крајот на краиштата, не можете да ги имате копчињата „Откажи“ и „Ок“ од иста боја, и ви се потребни за да реагирате на интеракциите со корисниците. 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 }); Па, добивате идеја: ќе има нешто диво, како поминување или додавање на автофокус – сите знаеме како изгледа едноставно во Фигма додека вистинскиот живот не удари тешко. width: 100% Сега замислете копче за линк: изгледа исто, но кога ќе го притиснете, треба да одите на надворешната или внатрешната врска. или Тагови секој пат, но ве молиме не. Можете исто така да додадете и Придобивки за вашата почетна компонента, но ќе се чувствувате задушени наскоро: <RouterLink> <a> to href <component :is="to ? RouterLink : href ? 'a' : 'button'" <!-- ugh! --> Се разбира, ќе ви треба компонента на "второ ниво" која го обвива копчето (исто така, ќе се справи со претпоставките за хиперлинкови и некои други интересни работи, но јас ќе ги пропуштам заради едноставноста): <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> И тука започнува нашата приказна. Square One Плоштад 1 Па, во основа тоа ќе работи, нема да лажам. Но нема да има автокомплет за деривативните приклучоци, што не е кул: <MyLinkButton :counter=“2"> Можеме тивко да ги шириме прописите, но ИДЕ не знае ништо за нив, и тоа е срам. Едноставно и очигледно решение е да ги шириме експлицитно: <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> Ќе функционира. ИДЕ ќе има соодветна автокомплета. Ќе имаме многу болка и жалење за поддршката. Очигледно, принципот „Не се повторувај себеси“ не беше применет тука, што значи дека ќе треба да го синхронизираме секое ажурирање. Еден ден, ќе треба да додадете уште еден проп, и ќе мора да најдете секоја компонента која го обвива основниот. Да, копчето и LinkButton веројатно се доволни, но замислете TextInput и десетина компоненти кои зависат од него: PasswordInput, EmailInput, NumberInput, DateInput, HellKnowsWhatElseInput. На крајот на краиштата, тоа е грдо. И колку повеќе приклучоци имаме, толку полошо станува. Clean It Up Чистење нагоре Тешко е повторно да се користи анонимен тип, па да му дадеме име. // MyButton.props.ts export interface MyButtonProps { theme?: ComponentTheme; small?: boolean; icon?: IconSvg; rightIcon?: IconSvg; counter?: number; disabled?: boolean; loading?: boolean; } Ние не можеме да извезуваме интерфејс од датотеки поради некои внатрешни магија, па ние треба да се создаде посебен На светлата страна, погледнете што имаме тука: .vue script setup .ts const props = withDefaults(defineProps<MyButtonProps>(), { theme: ComponentTheme.BLUE, icon: undefined, rightIcon: undefined, counter: undefined, }); Многу чист, нели? и еве го наследениот: interface MyLinkButtonProps { to?: RouteLocationRaw; href?: string; } const props = defineProps<MyButtonProps & MyLinkButtonProps>(); Сепак, тука е еден проблем: сега, кога основните приклучоци се третираат како „Пропагандата“ не се шири со Повеќе, па ние мораме да го направиме тоа сами. 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> Сето тоа е добро, но ние поминуваме малку повеќе отколку што сакаме: Како што можете да видите, сега нашиот основен копче, исто така, има Тоа не е трагедија, само малку неред и екстра бајтови, иако не е кул. 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> Сега, ние само го поминуваме она што мора да се помине, но сите тие буквали на струни не изгледаат неверојатно, нели? Interfaces vs Abstract Interfaces Интерфејси против апстрактни интерфејси Ако некогаш сте работеле со соодветни објектно-ориентирани јазици, веројатно знаете за такви работи како , што ни овозможува да ги добиеме метаподатоците за нашите структури. За жал, во TypeScript, интерфејсите се ефемерни; тие не постојат во текот на времето, и не можеме лесно да дознаеме кои полиња припаѓаат на . Рефлексија MyButtonProps Тоа значи дека имаме две опции.Прво, можеме да ги задржиме работите онакви какви што се: секогаш кога ќе додадеме Исто така, треба да се исклучи од (И дури и ако го заборавиме тоа, тоа не е голема работа). MyLinkButton propsToPass Вториот начин е да се користат објекти наместо интерфејси. Тоа може да звучи бесмислено, но дозволете ми да кодирам нешто: тоа нема да биде ужасно; ветувам. Страшно е Тоа // MyButton.props.ts export const defaultMyButtonProps: MyButtonProps = { theme: ComponentTheme.BLUE, small: false, icon: undefined, rightIcon: undefined, counter: undefined, disabled: false, loading: false, }; Нема смисла да се создаде објект само за да се создаде објект, но можеме да го користиме за стандардни прописи. станува уште почист: MyButton.vue const props = withDefaults(defineProps<MyButtonProps>(), defaultMyButtonProps); Сега само треба да ажурираме во : propsToPass MyLinkButton.vue const propsToPass = computed(() => Object.fromEntries( Object.entries(props).filter(([key, _]) => Object.hasOwn(defaultMyButtonProps, key) ) ) ); За да се направи оваа работа, ние треба експлицитно да се дефинира сите и Полето во Инаку, објектот не е „вклучен“. undefined null defaultMyButtonProps На овој начин, секогаш кога ќе додадете приклучок на основната компонента, исто така ќе треба да го додадете на објектот со претпоставлени вредности. Значи, да, тоа е повторно две места, и можеби тоа не е подобро од решението од претходното поглавје. I’m Done Јас сум направен Тоа не е ремек-дело, но тоа е веројатно најдоброто што можеме да го направиме во рамките на ограничувањата на TypeScript. Исто така, се чини дека има проп типови во SFC датотеката е подобро, но не можам да кажам дека преместувањето на нив во посебна датотека го направи многу полошо. Можете да го најдете кодот од овој напис на GitHub. GitHub