fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

טיפים קצרים למתכנתים מאת ינון פרק

نمایش بیشتر
1 418
مشترکین
+124 ساعت
-27 روز
-530 روز
آرشیو پست ها
ToCode
1 418
אני חייב להודות שאני עדיין לא רואה איך פקודת use תהפוך למשהו שאנשים ירצו להשתמש בו בלי עטיפה מסודרת של ספריה אבל ימים יגידו. בשביל המשחק מוזמנים להשוות בין הקוד הזה למנגנון המקביל של vue עליו כתבתי כאן: https://www.tocode.co.il/blog/2024-10-vue-async-fetch דעתי האישית שהמנגנון של vue הרבה יותר פשוט והרבה פחות מבלבל.

ToCode
1 418
קומפוננטות אסינכרוניות בריאקט 19 ריאקט 19 שוחרר ממש לפני כמה ימים והפיצ'ר הכי מרגש שהוא מביא לשולחן הוא התמיכה בקומפוננטות אסינכרוניות. בפוסט זה נראה איך זה עובד ולמה חשוב לשים לב. איך ליצור תוכנית ריאקט 19 עם Vite בגלל שהכל חדש תוכנית ריאקט חדשה שניצור ב vite עדיין עובדת עם ריאקט 18. כדי לשדרג לריאקט 19 יש לשנות את גירסאות הספריות מ 18 ל 19 בקובץ package.json. זאת הרשימה אצלי בפרויקט חדש ששודרג לריאקט 19:
{
  "name": "react-19-async-components",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": ">=19",
    "react-dom": ">=19"
  },
  "devDependencies": {
    "@eslint/js": "^9.15.0",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.15.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.14",
    "globals": "^15.12.0",
    "typescript": "~5.6.2",
    "typescript-eslint": "^8.15.0",
    "vite": "^6.0.1"
  }
}
יצירת קומפוננטה אסינכרונית הגדרת קומפוננטה אסינכרונית בריאקט לצערנו לא קורית עם פקודת async כמו הגדרה של פונקציה אסינכרונית רגילה אלא באמצעות שימוש ב Hook חדש שנקרא use. הפונקציה use יודעת לקבל Promise, והקומפוננטה "תופעל" רק כשה Promise מתממש. נשים לב שלפני שנוכל להפעיל את הקומפוננטה האסינכרונית עלינו לעטוף את העץ שיוצר אותה באלמנט Suspense של ריאקט, כלומר הקוד הראשי ב App.tsx הוא:
import { Suspense } from 'react'
import AsyncData from './AsyncData';
import './App.css'

function App() {
  return (
    <main>
    <h1>Async Component Demo</h1>
    <Suspense fallback={<p>Loading, please wait ...</p>}>
      <AsyncData />
    </Suspense>
  </main>
  )
}

export default App
בצד של הקומפוננטה עלינו ליצור Promise מחוץ לקומפוננטה, להעביר אותו פנימה לתוך פקודת use ולהשתמש במידע שחוזר ממנו:
import {use} from 'react';

const dataPromise = fetch(\https://swapi.dev/api/people/1\).then(r => r.json());

export default function() {
  const data = use(dataPromise);
  return (
    <div>
      <p>Ready!</p>
      <p>Received Data for: {data.name}</p>
    </div>
  )
}
שימו לב שה Promise נוצר מחוץ לפונקציית הקומפוננטה. יצירת Promise היא Side Effect ולכן אם נקרא ל fetch בתוך פונקציית הקומפוננטה כל פעם שריאקט יצטרך לרנדר את הקומפוננטה מחדש הוא יפעיל את ה fetch מחדש וניכנס ללולאה אינסופית של משיכת מידע ואז קריאה לפונקציית הקומפוננטה כדי לקבל את ה JSX, מה שיגרום להפעלה חדשה של ה fetch ומשיכת המידע מחדש וכן הלאה. אם אתם צריכים למשוך מידע כפונקציה של הקומפוננטה (למשל לפי id שעובר ב props) התבנית הכי טובה שמצאתי בינתיים היא לפצל את הקומפוננטה ל-2 ולשים בלוק Suspense נוסף בקומפוננטה העוטפת, כלומר קוד כזה:
import {Suspense, use, useState, useEffect} from 'react';

export default function(props: {id: number}) {
  const {id} = props;
  const [activeFetch, setActiveFetch] = useState(Promise.resolve({} as Record<string, string>));
  
  useEffect(() => {
    setActiveFetch(fetch(\https://swapi.dev/api/people/${id}\).then(r => r.json()))
  }, [id])

  return <Suspense>
      <ShowAsyncData dataPromise={activeFetch} />
    </Suspense>
}

function ShowAsyncData(props: {dataPromise: Promise<Record<string, string>>}) {
  const {dataPromise} = props;
  const data = use(dataPromise);
  return (
    <div>
      <p>Ready!</p>
      <p>Received Data for: {data.name}</p>
    </div>
  )
}
הסיבה ש useEffect לבד לא מספיק היא שה Suspense מחליף את הקומפוננטה שבתוכו בתוצאה של הקומפוננטה האסינכרונית, החלפה ששקולה לשינוי key, כלומר ממש יוצרים מחדש את הקומפוננטה - ולכן האפקט יופעל מחדש, אפילו ש id לא השתנה. לכן חייבים ליצור את ה Promise מחוץ לבלוק ה Suspense שיושפע מה use.

ToCode
1 418
ניסוי ריילס - שימוש ב Vue בלי Build Step אחד הטרנדים של ריילס היום הוא לוותר על ה Build Step ולהשתמש רק ב JavaScript. בניסוי היום כתבתי פרויקט ריילס שמשלב את vue כדי לראות איך זה עובד ובמה זה שונה מפרויקט צד-לקוח רגיל. איך Vue עובד ב JavaScript אנחנו רגילים לכתוב vue בקבצי vue ולהריץ את vite כדי להפוך את הכל ל JavaScript רגיל. אם רוצים לוותר על ה Build Step זה אומר לוותר על קבצי ה vue ולעבור לכתוב קבצי js ו HTML. יצרתי פרויקט ריילס ובתוכו קונטרולר בשם HelloWorld וכתבתי את הקוד הבא בקובץ התבנית viesws/hello_world/index.html.erb:
<div id='app'>
  <h1>HelloWorld#index</h1>
  <p>{{message}}</p>
  <Counter />
  <p>The end</p>
</div>

<script type="module">
  import { createApp, ref } from 'vue'
  import Counter from 'controllers/counter';

  createApp({
    setup() {
      const message = ref('Hello Vue!')
      return {
        message
      }
    }
  })
  .component('counter', Counter)
  .mount('#app')
</script>
יש לנו קובץ HTML עם תגית div בשם app ובתוכה התבנית שהייתי רגיל לרשום בתגית template בקובץ vue. מתחתיו בבלוק הסקריפט אנחנו רואים את הקוד שמייצר את ה"אפליקציה", שזה בעצם קוד ה vue שמתלבש לתוך התבנית. המשתנה message שמוגדר בסקריפט יוזרק לעמוד במקום המחרוזת {{message}} שנמצאת ב HTML. וכן בגלל שאנחנו ב Rails אפשר היה להשתמש בכל המנגנונים של ERB כדי ליצור את התבנית או לשתול משתנים מקוד צד השרת לתוך קוד הסקריפט שבתבנית. פיתוח קומפוננטה ב vue חלקים מהקוד שאפשר להשתמש בהם בכמה מקומות נקראים קומפוננטות. בדוגמה שלנו שימו לב לקומפוננטה counter שנשתלת בתוך התבנית אבל לא מוגדרת בקובץ הדוגמה. הקומפוננטה מוגדרת בקובץ JavaScript רגיל בשם app/javascript/controllers/counter.js ומיובאת בעזרת פקודת import. זה תוכן הקובץ:
import { ref } from 'vue';

export default {
  template: \
    <button @click="count++">{{count}}</button>
  \,
  setup() {
    const count = ref(0)
    return {
      count,
    }
  }
}
מדובר בקוד vue כמעט רגיל, רק שבמקום להשתמש בפורמט קובץ vue ותגית template אני מייצא אוביקט שיש לו מפתח בשם template. אין בעיה מתוך קומפוננטה כזו לקרוא לעוד קומפוננטות או להעביר פרמטרים לקומפוננטות בדיוק כמו ב vue רגיל. למעשה רוב הקוד של המערכת יהיה כנראה כתוב בקומפוננטות קטנות בקבצים כאלה, ובתוך סקריפט של כל דף (כל view) תהיה אפליקציית vue שתטען את הקומפוננטה או הקומפוננטות המרכזיות לאותו דף. מה עוד צריך הקובץ האחרון שמשחק תפקיד בניסוי הוא הקובץ config/importmap.rb שמגדיר את ה Import Maps, כלומר את המיפוי בין דברים שאני עושה להם import מתוך ה JavaScript לקבצי js אמיתיים על הדיסק. הוא נראה ככה:
* Pin npm packages by running ./bin/importmap *

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin "vue", to: "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
pin_all_from "app/javascript/controllers", under: "controllers"
וכאן אנחנו רואים את שם הספריה controllers ומבינים מאיפה זה הגיע (כן תבנית של פרויקט ריילס חדש משתמשת ב Stimulus ו controllers זה שם הספריה שבא משם). סך הכל הקוד עובד ונראה לא רע. יכול לתת פיתרון טוב כשרוצים לשלב ספריית Vue בפרויקט ריילס או כשרוצים לגוון קצת מ Stimulus. מוזמנים למצוא את הקוד המלא של פרויקט הדוגמה בגיטהאב כאן: https://github.com/ynonp/rails-vue-demo

ToCode
1 418
יצירתי או יצרני שתי מילים מאוד דומות בעברית עם משמעות שלפעמים נראית הפוכה. אנשים "פרודוקטיביים" מייצרים דברים וגורמים לחברה לפעול. אנשים "קריאייטיביים" ממציאים רעיונות חדשים, סיפורים, אומנות, שירים. בטח שכדאי להיות יצירתי אבל זה הפרודוקטיבי שמשלם את החשבון. או לפחות ככה חשבנו. בעולם של היום אלה שתי מילים שהולכות יחד יד ביד. אנחנו לא צריכים עוד מכוניות, עוד טלוויזיות או עוד נייר טואלט, אפילו אם תצליחו לייצר אותו קצת יותר יעיל או קצת יותר זול. הפרודוקטיביות היום היא קודם כל היכולת לראות מה אנשים צריכים ולהמציא פיתרונות לבעיות שאחרים עוד לא ראו. קריאייטיביות היא הבסיס לפרודוקטיביות.

ToCode
1 418
שתי דרכים להרגיע את TypeScript כשמשתמשים ב inject ב vue הקוד הזה עובד, אבל גורם לטייפסקריפט לכעוס:
<script setup lang="ts">
import { inject } from 'vue';
import { translations } from './InjectionKeys';

const texts = inject(translations);
</script>

<template>
  <button>{{ texts['click-here'] }}</button>
</template>
הקומפוננטה לוקחת מהקונטקסט את הערך הכי קרוב של המשתנה translations ואז מושכת ממנו את התרגום של הטקסט click-here. אין שגיאות בקונסול ועל המסך הכל נראה תקין. יותר מזה, אם בטעות מישהו שכח לעשות provide ל texts מיד נראה את השגיאה בקונסול כי ננסה לקרוא מפתח מתוך null. אני בסדר עם ההתנהגות הזאת כי אין לי מה לעשות עם המערכת אם אפילו הטקסטים לא נטענו. הבעיה שטייפסקריפט לא יודע את כל מה שאני יודע ומסמן את שורת הכפתור ב template באדום. מה עושים? שתי הצעות- דרך אחת היא להוסיף בדיקה בקוד הסקריפט אחרי inject:
<script setup lang="ts">
import { inject, ref } from 'vue';
import { translations } from './InjectionKeys';

const texts = inject(translations);
if (!texts) {
  throw new Error("Missing texts in parent component");
}
</script>

<template>
  <button>{{ texts['click-here'] }}</button>
</template>
קיבלנו הודעת שגיאה קצת יותר טובה וטייפסקריפט רגוע. דרך שניה היא להעביר פרמטר שני ל inject שהוא ערך ברירת המחדל:
<script setup lang="ts">
import { inject, ref } from 'vue';
import { translations } from './InjectionKeys';

const texts = inject(translations, ref<Record<string, string>>({}));
</script>

<template>
  <button>{{ texts['click-here'] }}</button>
</template>
פה הקוד יותר קצר שזה נחמד, אבל החיסרון הוא שעכשיו אם מישהו שוכח להעביר את texts מקומפוננטה עליונה יותר לא נראה שגיאה בקונסול אלא רק טקסטים ריקים. כנראה שגם זה לא נורא כי זאת שגיאה שמאוד קל לזהות. איזה משתי הגישות אתם מעדיפים? או שאולי יש לכם טכניקה אחרת? מוזמנים לספר בתגובות או בטלגרם.

ToCode
1 418
עוד כמה בעיות עם עבודה מיותרת הפוסט כאן נוגע בנקודה טובה לגבי SQL: https://www.depesz.com/2024/12/01/sql-best-practices-dont-compare-count-with-0/ בקצרה הוא מסביר למה לא כדאי לכתוב את זה:
SELECT u.* FROM users u
WHERE 0 = (SELECT COUNT(*) FROM addresses a WHERE a.user_id = u.id);
כשבעצם היה צריך לכתוב את זה:
SELECT u.* FROM users u
WHERE NOT EXISTS (SELECT FROM addresses a WHERE a.user_id = u.id);
אבל אני חושב שהנקודה פה יותר גדולה מ SQL ויותר גדולה מבעיית ביצועים אחת פחות: 1. קוד רע משתכפל - כשאני בודק אם count של משהו שווה 0, גם אם במקרה הספציפי שלי זה לא שבר את המערכת, מישהו אחר יעתיק את זה במקום אחר ושם אולי זה יישבר. מבט קדימה על עולם בו AI יכתוב קוד, תבניות רעות בקוד שלנו הולכות להשתכפל הרבה יותר. 2. קוד רע הוא מורה רע - מתכנתים צעירים שיעבדו על מערכת עם הרגלים רעים יאמצו את אותם הרגלים רעים ואפילו לא ידעו שקיימת דרך יותר טובה. וככה אנחנו מקבלים מפתחים עם 5 שנים ניסיון ב SQL שעדיין יהיו מופתעים לגלות שיש דבר כזה EXISTS, כי הם אף פעם לא ראו אותו בקוד שלהם. ופה החשיבות של היכרות טובה עם השפה: ככל שאני מכיר טוב יותר את כל הפקודות בשפה קל לי יותר למצוא את הפקודה שפותרת לי את הבעיה בצורה מדויקת, יעילה ומעבירה את המסר הנכון לאלה שיקראו את הקוד.

ToCode
1 418
הצעה לפרויקט - מישהו ללמוד איתו לימוד יחד עם חברים יכול להיות הרבה יותר אפקטיבי מלימוד לבד: המחויבות החברתית מגבירה את המוטיבציה, חברים יכולים לעזור כשאנחנו נתקעים ולהעלות שאלות שלא חשבנו עליהן. עכשיו עם ה AI אפילו לא צריך מורה שילווה אותנו. בהינתן נושא, חומרי לימוד ורשימת משימות מתכנתים בעלי מוטיבציה יכולים להגיע מאוד רחוק. והנה רעיון לפרויקט צד נחמד ולא קשה מדי, מערכת להקמה ותפעול קבוצות לימוד. בגדול: 1. משתמשים יכולים להיכנס ולהקים מסלולי לימוד. מסלולי הלימוד יוכלו להיות בתשלום או בחינם, והם כוללים סילבוס מסודר, משימות ואולי פיתרונות עם הסברים. 2. משתמשים אחרים יכולים להיכנס ולהקים קבוצות לימוד. כשאני מקים קבוצת לימוד אני בוחר את השעות והימים בהם מסתדר לי ללמוד, מספר על עצמי ומה הרמה שלי ועם איזה סוג מפתחים הייתי רוצה ללמוד. בהקמת קבוצת לימוד אני גם בוחר את מסלול הלימוד של הקבוצה. 3. משתמשים אחרים יכולים להיכנס, לחפש קבוצות לימוד ולהצטרף. אני חושב שהמשחק של קבוצות לימוד יוצא מהתבנית של אתרי קורסים ומשנה את הפוקוס, במקום לבחור קורס לפי ביקורות או סילבוס, אני בוחר לפי האנשים איתם אני רוצה ללמוד. הרבה פעמים התוצאה תהיה לימוד יותר אפקטיבי. חושבים לבנות את הפרויקט ורוצים להתייעץ על טכנולוגיה או ארכיטקטורה? אל תתביישו להשאיר הודעה. בניתם אותו או משהו דומה? ספרו לי על זה ואשמח להשתמש ולעזור לכם לקדם.

ToCode
1 418
איך לענות על שאלת ראיון עבודה מישהו פרסם שאלת ראיונות עבודה טפשית על JavaScript ושאל מה יהיה הפלט של התוכנית הבאה:
function sayHi() {
  console.log(name);
  console.log(age);
  var name = 'Lydia';
  let age = 21;
}

sayHi();
זה לא חשוב עכשיו מה התשובה. אתם יכולים ללכת ל Chat GPT לברר. מה שיותר מעניין זה מבנה התשובה של אותו Chat GPT: 1. המשפט הראשון מסביר את המטרה של השאלה - זו שאלה שבודקת את ההיכרות שלך עם הרעיונות של Variable Hoisting ו Block Scoping ב JavaScript. וזאת בדיוק הדרך שכדאי להתחיל תשובה לשאלות גם בראיון אמיתי. 2. אחרי זה Chat GPT מסביר על הרעיונות ומספר מה ההבדל בין var ו let בהקשר של גישה למשתנה לפני שורת ההגדרה שלו. 3. לאחר מכן הוא עובר שורה שורה ומסביר מה היא עושה ולמה (זה קצת מיותר בעיניי). 4. ובסוף מראה את פלט התוכנית. שמתי לב שהדבר שהכי הרשים אותי במנגנון היה כשהדבקתי את השאלה ו Chat GPT כתב את שמות הנושאים אותם השאלה באה לבחון. שימוש בשמות הנכונים מראה לי שהתשובה מתיחסת בדיוק לדברים שרציתי לבדוק ומניח את היסודות לתקשורת טובה על נושאים אלה.

ToCode
1 418
סיפור, משפטים, מילים. לסיפור יש התחלה, אמצע וסוף. לפעמים גם עלילה וכשיש לנו מזל הוא מעורר מחשבה ומעביר מסר. את כל האלמנטים האלה סופרים בונים באמצעות משפטים המורכבים ממילים. סופרים שונים יכולים לכתוב עלילות דומות ומסרים דומים ולהשתמש במילים אחרות לגמרי - ולמרות העלילה הזהה ברור שזה לא יהיה אותו סיפור. הפרטים חשובים. גם הקוד שלנו מספר סיפור, וגם את הקוד אנחנו בונים מחיבור של קטעי קוד קטנים יותר דרך תבניות. ואפילו אם Chat GPT מסוגל לקבל משימה ולהציע מימוש מסוים, הוא לעולם לא יחליף את המתכנתים שיכולים לכתוב מספר מימושים ולנווט ביניהם, כמו אותם סופרים מיומנים שיכולים לנסח בדרכים שונות את אותם רעיונות. וכן תכנות זו עבודה יצירתית, והיצירתיות דורשת היכרות מעולה עם תבניות כדי שאפשר יהיה לשלב אותן (ולשבור אותן) בדרכים שמתאימות למערכת עליה אנחנו עובדים. מתכנתים משתפרים ככל שכותבים יותר פרויקטים ותוכניות, כשם שסופרים משתפרים ככל שיכתבו יותר סיפורים. אבל בעבודה רק על פרויקטים מלאים יש תמיד את הסכנה לדלג על דברים קטנים, לחזור שוב ושוב על טכניקות לא מספיק טובות, רק בגלל שאנחנו כבר רגילים אליהן. לכן בנוסף לעבודה על פרויקטים גדולים מתכנתים טובים משקיעים גם זמן בשיפור אותן תבניות קטנות שמרכיבות את הקוד שלנו - תוכניות של עשרות שורות לכל היותר שפותרות בעיה ספציפית, מעבר על מספר פיתרונות, השוואה ביניהם וחיפוש הפיתרון הפשוט והטוב ביותר. וכן גם ההבנה שלפעמים רעיון טוב נשבר כשהדרישות משתנות, וההימור איזה דרישות הולכות להשתנות. את כל העבודה הזאת משלב בצורה מופלאה אריק ווסטל כל שנה בפרויקט Advent Of Code שלו, שם הוא משתף כל יום במהלך דצמבר חידת תכנות קטנה המורכבת משני חלקים, ורק אחרי פיתרון החלק הראשון מופיע החלק השני. החידות ממוקדות בכוונה ומאפשרות להתאמן על החלקים הקטנים והספציפיים ביותר בקוד: להבין את הדרישות, לפתור בעיות, לדמיין איזה שינויים בדרישות כנראה יגיעו ואז לראות את השינויים בדרישות של אריק ולהתאים את הפיתרון שלנו לדרישות החדשות. היום הוא היום הראשון של דצמבר והחידה הראשונה של Advent Of Code בדיוק התפרסמה. בהצלחה. https://adventofcode.com

ToCode
1 418
ראוטרים נהיו מאוד מסובכים בשנים האחרונות וטאנסטאק ראוטר, למרות היותו עדיין יותר פשוט מ React Router, עדיין נשאר במגרש של הראוטרים המסובכים. מה שמסבך אותו לדעתי זה מנגנון ה Data Fetching המובנה, שמכריח את הראוטר לכלול כלים לניהול ה Cache של המידע שמגיע מהשרת. אישית העדפתי את הימים ששני המנגנונים האלה היו באחריות ספריות שונות - כלומר אפשר היה להשתמש ב react-query או RTK Query בשביל שליפת מידע ובספריה אחרת בשביל הניתוב. בכל מקרה זה בסך הכל חלומות נוסטלגיים שלי כי לא נראה שתהיה דרך חזרה. ספציפית לגבי Tanstack Router התלונה היחידה שלי היא שבגלל הטייפסקריפט הוא יכול להיות קצת נודניק. קודם כל בשביל שקובץ ה routeTree.gen.ts יתעדכן הפרויקט צריך לרוץ במצב פיתוח, כלומר npm run dev צריך לעבוד. נכון רוב הזמן אני משאיר אותו דולק אבל לפעמים אני גם כותב קוד כשהוא סגור ואז צריך להתעצבן שהטייפסקריפט לא מזהה את הנתיבים שיצרתי. דוגמה שניה היא נתיבים עם פרמטרים, שדורשים תחביר מיוחד ב Link:
<Link to="/posts/$postId" params={{postId: "7"}}>Post 7</Link>
ניסיון לכתוב את זה בתור מחרוזת פשוטה שובר את טייפסקריפט, כלומר זה לא עובד:
<Link to="/posts/7">Post 7</Link>
אבל מלבד הבעיות עם הטייפסקריפט העבודה עם טנסטאק בהחלט נעימה. ניתוב בעזרת מערכת קבצים עוזר לשמור על סדר בפרויקט וגם דרך הטיפול שלהם בפרמטרים מאוד ברורה רק מהסתכלות על מערכת הקבצים. בפוסטים הבאים אני מקווה להמשיך לחקור אותו ולכתוב יותר לעומק על Data API, על הטיפול ב Redirects ועל פקודות ניתוב מתוך הקוד.

ToCode
1 418
עשר דקות עם Tanstack Router עכשיו ש React Router עדכנו שוב את הגירסה, הפעם כדי להפוך לפריימוורק במיזוג עם Remix, אני חושב שהגיע הזמן לבחון אפשרויות אחרות, והאפשרות הפופולרית ביותר היום מלבד React Router היא Tanstack Router. בואו נראה קצת קוד לדוגמה כדי להבין איך זה עובד. קוד הדוגמה של הפוסט זמין בגיטהאב בקישור: https://github.com/ynonp/tanstack-router-simple-demo קובץ ה Layout טנסטאק ראוטר, בדומה ל next.js, עובד לפי שמות קבצים. הוא מתחבר ל vite וכל פעם שאנחנו רצים במצב פיתוח או בנייה הוא מחפש את הקבצים שנראים כמו נתיבים בתיקייה ויוצר מכולם יחד קובץ מידע מרוכז על נתיבים, בו הוא ישתמש בקוד כדי לספק Type Safety, כלומר אלמנט Link יוכל לדעת אם הוא מוביל למקום שבאמת מתאים לנתיב מתוך ה Router ואם היתה שגיאת כתיב אז נוכל לראות את זה כבר בזמן כתיבת הקוד. בשביל להתקין את Tanstack Router יש תהליך די מסורבל שמפורט בדף ההתקנה שלהם: https://tanstack.com/router/latest/docs/framework/react/installation בגדול מוסיפים אותו ל vite, וטוענים אותו מהקובץ הראשי ביישום. אחרי ההתקנה אפשר ליצור את קבצי הנתיבים וקובץ הנתיב הראשי בדוגמה שלהם נקרא __root.tsx. זה הקוד שהם כתבו עם קצת שינויים שלי:
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'

export const Route = createRootRoute({
  component: () => (
    <>
      <div className="p-2 flex gap-2">
        <Link to="/" className="[&.active]:font-bold">
          Home
        </Link>{' '}
        <Link to="/about" className="[&.active]:font-bold ">
          About
        </Link>
        <Link
          to="/posts/$postId"
          params={{postId: "7"}}
          className="[&.active]:font-bold ">
          Read Post
        </Link>
      </div>      
      <hr />
      <Outlet />
      <TanStackRouterDevtools />
    </>
  ),
})
הפקודה createRootRoute אומרת שמדובר בנתיב ראשי כלומר קובץ Layout. אוביקט ה component שלה הוא הקומפוננטה הראשית של העמוד ובתוכו קומפוננטת Outlet תציג את התוכן האמיתי כלומר את הקומפוננטה שמתאימה לנתיב הנוכחי. מעל ה Outlet יש לנו תפריט ניווט ומתחת יש לנו קומפוננטה של כלי פיתוח. כלי הפיתוח זה בעצם חלון צף קטן שנותן המון מידע על ה Router והנתיבים השונים בו. קבצי הנתיבים שני הקבצים האחרים בתוכנית הדוגמה שהוצעה בתיעוד הם about.lazy.tsx ו index.lazy.tsx. השם שלהם כבר מסמן לנו שמדובר בנתיבים עצלים, כלומר שהקוד שלהם ייטען רק כשמשתמש ייכנס אליהם בפעם הראשונה. כך נראה תוכן הקובץ about.tsx:
import { createLazyFileRoute, Link } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/about')({
  component: About,
})

function About() {
  return <div className="p-2">
    Hello from About!
    <Link to='/'>Type Safe?</Link>
  </div>
}
לא מסובך: הפקודה createLazyFileRoute מקבלת את הנתיב וקומפוננטה ומוסיפה אותו לרשימת הנתיבים (זאת שנוצרת בצורה אוטומטית בקובץ routeTree.gen.ts). ה Link שמופיע שם הוא Type Safe, כלומר אם אני מנסה לכתוב משהו שלא מתאים לשום נתיב מופיע שם קו אדום בגלל הטייפסקריפט. נתיב דינמי בשביל המשחק יצרתי קובץ נוסף בשם posts/$postId.lazy.tsx עם התוכן הבא:
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts/$postId')({
  component: Post,
})

function Post() {
  const { postId } = Route.useParams()
  return <div className="p-2">This is post number: {postId}</div>
}
שם הקובץ כבר מרמז לנו שיש פה משהו מיוחד. הדולר בתחילת שם הקובץ אומר ש postId זה שם של פרמטר ובאמת בתוך גוף הקומפוננטה אני יכול להשתמש ב useParams כדי לגשת לערך של אותו פרמטר. פה אפשר להוציא בקשת רשת כדי למשוך מידע לגבי אותו פוסט לפי המזהה שלו, או להיעזר במנגנון ה Data Fetching המובנה של Tanstack Router (עליו אולי אכתוב בפוסט אחר) כדי לשלב את הראוטר עם מנגנון שליפת המידע. על מה אפשר להתלונן?