8,559 קריאות
8,559 קריאות

Vue.js: להפיץ פרופסורים כמו פרו

על ידי Andrei Sieedugin7m2025/05/03
Read on Terminal Reader

יותר מדי זמן; לקרוא

כאשר אתה יוצר רכיבים עבור הפרויקט שלך, הכל מתחיל כיף וקל. ליצור 'MyButton.vue' ולהוסיף קצת סגנון, ו voilà. אז אתה מיד מבין שאתה צריך תריסר תוספים, כי צוות העיצוב שלך רוצה שזה יהיה בצבעים שונים וגודלים, עם סמלים בצד שמאל ושמאל, עם חשבונות. אחרי הכל, אתה לא יכול לקבל את הכפתורים "בטל" ו "OK" של אותו צבע, ואתה צריך אותם כדי להגיב אינטראקציות משתמש.
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
});


זה עדיין בסדר; זה הגיוני. אחרי הכל, אתה לא יכול לקבל את הכפתורים "בטל" ו "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
});


ובכן, אתה מקבל את הרעיון: יהיה משהו פראי, כמו עוברwidth: 100%או הוספת ריכוז אוטומטי – כולנו יודעים איך זה נראה פשוט ב-Figma עד שהחיים האמיתיים פוגעים קשה.


עכשיו לדמיין כפתור קישור: זה נראה אותו דבר, אבל כאשר אתה לוחץ עליו, אתה צריך ללכת קישור חיצוני או פנימי.<RouterLink>או<a>תגיות בכל פעם, אבל בבקשה לא.toוhrefהיתרונות של המרכיב המקורי שלך, אבל אתה תרגיש מוחלף בקרוב:

<component
  :is="to ? RouterLink : href ? 'a' : 'button'"
  <!-- ugh! -->


כמובן, תזדקק לרכיב "רמה שנייה" אשר מכסה את הכפתור שלך (זה גם יטפל בהקצאות היפר-קישור default וכמה דברים מעניינים אחרים, אבל אני אשחרר אותם למען הפשטות):

<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">אבל לא יהיה autocomplete עבור תוספים נגזרים, אשר לא מגניב:


Only "href" and "to"


אנחנו יכולים להפיץ תרומות בשקט, אבל 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 יהיה autocomplete מתאים. יהיה לנו הרבה כאב ומצטער לתמוך בו.


ברור, העיקרון של "אל תחזור על עצמך" לא היה מיושם כאן, מה שאומר שאנחנו נצטרך לסנכרן כל עדכון. יום אחד, תצטרך להוסיף תמיכה נוספת, ותצטרך למצוא כל רכיב אשר מכסה את הבסיס. כן, כפתור ו- 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's props, הם לא מתפשטים עם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

תגיות abstract interfaces

אם אי פעם עבדת עם שפות אוריינטציה אובייקטים מתאימות, אתה כנראה יודע על דברים כגון:השתקפותלמרבה הצער, ב-TypeScript, ממשקים הם אפשריים; הם אינם קיימים בזמן ביצועים, ואנחנו לא יכולים בקלות לגלות אילו שדות שייכים ל-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.


זה גם נראה כי יש סוגים prop בתוך קובץ SFC הוא טוב יותר, אבל אני לא יכול לומר כי העברת אותם לקובץ נפרד גרמה לכך הרבה יותר גרוע.


אתה יכול למצוא את הקוד מאמר זה על GitHub.

Github

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks