לא שוב טייפסקריפט

11/11/2024

בואו נכתוב קומפוננטת Vue שיכולה להיות מופעלת בשני אופנים - או שאפשר להעביר לה כ Property מספר או שמעבירים לה שתי מחרוזות. אם מעבירים מספר אז המחרוזת הראשונה תהיה המספר שעבר והמחרוזת השנייה תהיה טקסט קבוע. ניסיון ראשון עשוי להיראות כך:

<script setup lang="ts">
const props = defineProps<
  | {number: number}
  | {text1: string, text2: string}>();

const text1 = "number" in props ? String(props.number) : props.text1;
const text2 = "number" in props ? "Great Number!" : props.text2;
</script>

<template>
  <p>{{ text1 }}</p>
  <p>{{ text2 }}</p>
</template>

והקריאה לקומפוננטה עשויה להיראות כך:

<Demo :number="5" />
<Demo text1="hello" text2="world" />

זה עבד מבחינת טייפסקריפט אבל הקוד עצמו נכשל. בהגדרת props עם defineProps ב Vue, אוביקט הפרופס תמיד מכיל את כל הפרופס האפשריים, כלומר בשתי ההפעלות הוא יקבל גם את number, גם את text1 וגם את text2, פשוט בהפעלה הראשונה number מקבל את הערך 5 והטקסטים יהיו undefined ובהפעלה השניה זה number שיהיה undefined והטקסטים מקבלים את הערכים הנכונים שלהם.

רעיון אחד לצאת מהסיפור עשוי להיות לעדכן את הקוד שמושך את הטקסטים מ props ולתקן את הבדיקה. הבעיה שזה לא ממש עובד:

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;

טייפסקריפט לא מאפשר לגשת ל props.number כי מבחינתו אולי אוביקט ה props לא כולל את number. מה שכן יעבוד הוא לעדכן את defineProps כך שכל אפשרות תכיל התיחסות לדברים באפשרות השניה אבל עם טיפוס never כדי שלא יעבירו ערכים למפתחות אלה:

const props = defineProps<
  | {number: number, text1?: never, text2?: never}
  | {number?: never, text1: string, text2: string}>();

const text1 = props.number ? String(props.number) : props.text1;
const text2 = props.number ? "Great Number!" : props.text2;

הקוד הזה עובד ובנוסף יש לנו בדיקת טייפסקריפט טובה. שתי השורות האלה תקינות:

<Demo :number="5" />
<Demo text1="hello" text2="world" />

ושלושת אלה מראות פס אדום על הקומפוננטה כדי שנזהה הפעלה לא נכונה:

<Demo :number="5" text1="hello" text2="world"  />
<Demo text1="hello"  />
<Demo  />