プロジェクトのコンポーネントを作成すると、すべてが楽しく簡単に始まります。 少しスタイリングを加え、これです。 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 }); 結局のところ、同じ色の「キャンセル」と「OK」ボタンは存在しませんし、ユーザーの相互作用に反応するためにそれらが必要です。 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 }); Well, you get the idea: there will be something wild, like passing もしくは自動焦点を加えることで、私たちは皆、現実の生活が激しく打つまで、Figmaで簡単に見えることを知っています。 width: 100% Now imagine a link button: it looks the same, but when you press it, you should go to the external or internal link. リンクボタンを想像してください:それは同じように見えますが、それを押すとき、あなたは外部または内部のリンクに移動する必要があります。 または 毎回タグを追加しますが、追加しないでください。 そして あなたの最初のコンポーネントに利点がありますが、あなたはすぐに窒息を感じるでしょう: <RouterLink> <a> to href <component :is="to ? RouterLink : href ? 'a' : 'button'" <!-- ugh! --> もちろん、ボタンを巻き込む「第2レベルの」コンポーネントが必要になります(デフォルトのハイパーリンクの概要や他の興味深いものも処理しますが、シンプルさのためにそれらを省略します): <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 うん、基本的にそれは働くでしょう、私は嘘をつかないでしょう. You can still type , and it'll be fine. But there'll be no autocomplete for the derived props, which's not cool. しかし、 derived propsのための自動完成はありません。 <MyLinkButton :counter=“2"> 私たちは黙々とプロポーズを広めることができますが、IDEはそれらについて何も知らないし、それは恥ずかしいことです。 シンプルで明白な解決策は、それらを明示的に広めることです: <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> IDEは適切な自動完成を有するだろう. 我々は多くの痛みと後悔を支持するだろう。 明らかに、「自分を繰り返さないで」という原則はここで適用されていなかったため、すべてのアップデートを同期する必要があります。ある日、あなたは別のプロプションを追加する必要があります、そしてあなたは基本的なコンポーネントを包み込むすべてのコンポーネントを見つける必要があります。はい、ボタンとLinkButtonはおそらく十分ですが、テキストインプットとそれに依存する数十のコンポーネントを想像してください:パスワードインプット、EmailInput、NumberInput、DateInput、HellKnowsWhatElseInput。プロプションを追加することは苦痛を引き起こすべきではありません。 結局のところ、それは醜いです。そして私たちが持っているプロップが多ければ多いほど、それは醜くなります。 Clean It Up Clean It Up 匿名のタイプを再利用するのはかなり難しいので、名前を付けましょう。 // MyButton.props.ts export interface MyButtonProps { theme?: ComponentTheme; small?: boolean; icon?: IconSvg; rightIcon?: IconSvg; counter?: number; disabled?: boolean; loading?: boolean; } インタフェースから輸出することはできません。 内部ファイルのせいで 魔法だから、別々のものを作らねばならない。 file. On the bright side, look what we have here: 明るい側で、私たちがここに持っているものを見てください。 .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>(); しかし、ここに問題があります:今、基本的なプロプは、 props, they are not propagated with 彼らは、拡散されていません。 もうこれ以上、自分でやらなければいけません。 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> 今、私たちは通過しなければならないものを渡すだけですが、それらの文字列の文字列はすべて素晴らしいとは思えませんか? そしてそれは最も悲しいTypeScriptのストーリーです、皆さん。 Interfaces vs Abstract Interfaces インターフェイス vs 抽象インターフェイス 適切なオブジェクト指向言語で働いたことがあるなら、おそらくあなたは、 残念ながら、TypeScript では、インターフェイスはエフェミリアルであり、ランタイムでは存在しませんし、どのフィールドがランタイムに属するかを簡単に知ることはできません。 . 反省 MyButtonProps それは我々が2つの選択肢を持っていることを意味します。第一に、我々は物事をそのままにすることができる:我々がプロプを追加するたびに また、これを除外することも必要です。 (たとえそれを忘れても、それは大きな問題ではありません)。 MyLinkButton propsToPass 2つ目の方法は、インターフェイスの代わりにオブジェクトを使用することです。それは無意味に聞こえるかもしれませんが、何かをコードさせてください:それは恐ろしいことはありません。 恐ろしい あの // 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) ) ) ); この作業を行うには、すべてを明確に定義する必要があります。 そして フィールド in そうでなければ、オブジェクトは「HaveOwn」ではありません。 undefined null defaultMyButtonProps このようにして、基本コンポーネントにプロンプトを追加するたびに、デフォルト値を持つオブジェクトにもプロンプトを追加する必要がありますので、はい、再び2つの場所があり、もしかしたら前章のソリューションよりも良いものではありません。 I’m Done I'M DONE それは傑作ではありませんが、TypeScriptの限界の中でできる最善のことです。 また、SFCファイル内にプロプトタイプがある方が良いように見えますが、それらを別々のファイルに移すことがより悪化したとは言えませんが、プロプトの再利用は確実に良くなりましたので、私たちが仕事と呼ぶ無限の戦いの小さな勝利と考えます。 この記事のコードはGitHubでご覧いただけます。 GitHub