8,559 читања
8,559 читања

Vue.js: Промовирање на профили како про

од страна на Andrei Sieedugin7m2025/05/03
Read on Terminal Reader

Премногу долго; Да чита

Кога ќе креирате компоненти за вашиот проект, сè започнува забавно и лесно. Креирајте 'MyButton.vue' и додадете малку стил, и еве. Тогаш веднаш сфаќате дека ви треба десетина прописи, бидејќи вашиот дизајнерски тим сака да биде од различни бои и големини, со икони на лево и десно, со бројачи. На крајот на краиштата, не можете да ги имате копчињата "Откажи" и "Ок" од иста боја, и ви се потребни за да реагирате на корисничките интеракции.
featured image - Vue.js: Промовирање на профили како про
Andrei Sieedugin HackerNoon profile picture

Кога ќе креирате компоненти за вашиот проект, сè започнува забавно и лесно.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">Но нема да има автокомплет за деривативните приклучоци, што не е кул:


Only "href" and "to"


Можеме тивко да ги шириме прописите, но ИДЕ не знае ништо за нив, и тоа е срам.


Едноставно и очигледно решение е да ги шириме експлицитно:

<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>


Сето тоа е добро, но ние поминуваме малку повеќе отколку што сакаме:


W3C disapproves


Како што можете да видите, сега нашиот основен копче, исто така, има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
L O A D I N G
. . . comments & more!

About Author

Andrei Sieedugin HackerNoon profile picture
Andrei Sieedugin@smileek
Senior frontend developer with product management experience

ВИСЕТЕ ТАГОВИ

ОВОЈ СТАТИЈА БЕШЕ ПРЕТСТАВЕН ВО...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks