ch
Feedback
ToCode

ToCode

前往频道在 Telegram

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

显示更多
1 419
订阅者
无数据24 小时
无数据7
-530
帖子存档
ToCode
1 419
כתבתי פה לפני כמה ימים על השינוי הזה בריאקט 19 שאמור לקלקל את ריאקט קוורי אז לשמחתנו החברים הקשיבו לצעקות והסיפור כרגע הוקפא https://blog.codeminer42.com/how-react-19-almost-made-the-internet-slower/

ToCode
1 419
פוחד אפילו לנסות (סביבת תרגול) לכתוב אפליקציית ווב בלי פריימוורק? אני פוחד אפילו לנסות. מאיפה אני אקח את כל דוגמאות הקוד? איך אנהל סטייט גלובאלי? ומה אם הקוד יסתבך ויצא משליטה? בפרויקט ללקוח או במקום עבודה אין דבר יותר טבעי מלבחור את הטכנולוגיה המבוססת, זאת שיהיה הכי קל לגייס אנשים עבורה, להיעזר ב Chat GPT בשאלות והכי חשוב לדעת שלא משנה מה יקרה תמיד יהיה מוצא. אנשים אחרים כבר כתבו יישומים דומים בריאקט ולכן גם אני אסתדר. אבל בפרויקט צד? בפרויקט ללימודים? בקורס? אחת המטרות של פרויקטים נטולי סיכון היא לקבל מרחב בטוח להתנסות, מרחב שבו אפשר לכתוב אפליקציה בגישת Micro Services גם אם זה Overkill, להכניס בסיס נתונים מבוזר (או 5 בסיסי נתונים), לעצב דף בית תלת מימדי עם Three.js וכן גם לכתוב את ה Front End בלי אף פריימוורק ואת צד השרת ב Rust. לא יודע איזה מהרעיונות האלה יעבוד לפרויקט שלכם וכמה מפרויקטי הלימודים האלה באמת יצליחו להתרומם, אבל מה שבטוח שבסיום הכתיבה תהיה לכם הבנה טובה של שיטת עבודה חדשה, ואולי חלק משיטות העבודה האלה יעזרו לכם גם לפרויקטים אמיתיים.

ToCode
1 419
עוד כמה טיעונים נגד טייפסקריפט הטיעון הראשון נגד טייפסקריפט הוא שאין צורך היום להוסיף שלב "בנייה" ועדיף לשלוח לדפדפן ישירות את הקוד שאנחנו כותבים, במיוחד מאחר ודפדפנים תומכים ב ES Modules ו Import Maps. אני שם את הטיעון הזה בצד כי האמת שהתרגלנו לשלב הבנייה. הטיעון השני נגד טייפסקריפט יותר מעניין - והוא שטייפסקריפט פשוט לא מספיק טובה ואפילו במקרים פשוטים צריכים להוסיף מעקפים. קוד לדוגמה:
type Table = Array<Array<number>>;

function duplicateRow(table: Table, rowIndex: number): Table {
    if ((rowIndex >= table.length) || (rowIndex < 0)) {
        throw new Error("row index out of range")
    }

    return [...table.slice(0, rowIndex), table.at(rowIndex), ...table.slice(rowIndex)];
}
הקוד הזה אינו טייפסקריפט תקני כי טייפסקריפט לא יודע ש at חייב להחזיר שורה מתוך המערך כי כבר וידאנו שהאינדקס נמצא בטווח שורה מעל. וכן אני יכול להחליף את ה at בסוגריים מרובעים ואז הכל יתקמפל, ואני יכול לבקש מטייפסקריפט להתעלם מהשגיאה ואני יכול להוסיף as וכל הטריקים האלה, אבל זה בדיוק סוג השינויים שאנחנו לא רואים את הערך שלהם. וטיעון שלישי הוא הקהילה. גם אם אנחנו מצליחים להתמודד עם הסיבוכיות בקוד שלנו הרבה מאוד ספריות טייפסקריפט כוללות אינסוף טריקים של השפה כדי ליצור בצורה אוטומטית את הגדרות הטיפוסים לפי קוד שאנחנו כותבים. לדוגמה רידאקס טולקיט לוקחת את קוד ה Reducers ויוצרת ממנו הגדרות טיפוסים, אבל אם יש טעות קטנה בקוד או אפילו מבנה שהוא לא בדיוק מה שכותבי Redux Toolkit התכוונו אנחנו יכולים למצוא את עצמנו מול הודעות שגיאה איומות שייקח הרבה זמן לפענח. או אפולו שמכניסה להגדרות הטיפוסים ממש את שאילתות ה GraphQL (כמחרוזת מילה במילה) ואז אם אחרי יצירת הטיפוסים אני משנה אפילו פסיק או רווח בשאילתה פתאום כל מערכת הטיפוסים מפסיקה לעבוד. או קיסלי שמבין לבד את מבנה הטבלאות וגוזר ממנו דינמית הגדרות טיפוסים כדי ששאילתות שאנחנו כותבים יהיו Type Safe ובדרך מביא את טייפסקריפט לקצה. כל עוד זה עובד וכל הגדרות הטיפוסים המוזרות מוכלות רק בתוך קוד הספריה הכל בסדר, אבל כמעט בכל ספריה מגיע רגע שצריך להיכנס פנימה לקוד שלהם כדי להבין איך לכתוב את הטיפוסים כך שטייפסקריפט יהיה שמח. אני עדיין אוהב טייפסקריפט וחושב שהיא טובה יותר מהאלטרנטיבה של כתיבה ללא טיפוסים לרוב הפרויקטים, אבל אי אפשר להתעלם מהמורכבות של השפה והאקוסיסטם.

ToCode
1 419
אנחנו כבר לא חושבים ככה יותר אחד הדברים שאהבתי בריאקט זה שהם כמעט אף פעם לא שוברים תאימות אחורה מבחינת קוד. הם מבינים שיש אנשים שמשתמשים במוצרים שלהם בפרודקשן, שרוצים להישאר מעודכנים ולהשתמש בגירסאות חדשות של ריאקט אבל לא תמיד יכולים או רוצים לשנות את סגנון הכתיבה שלהם. וככה למרות ש Functional Components זה הדבר, עדיין אפשר להשתמש ב Class Components והכל יעבוד. ולמרות ש Concurrent Mode זה הדבר, עדיין אפשר להפעיל ריאקט בלעדיו והכל יעבוד. אבל בואו לא נטעה - לצוות הפיתוח יש דעה, הדעה שלהם באה לידי ביטוי בשינויים שנכנסים לפריימוורק והדעה שלהם גם יכולה להשתנות. הטקסט הבא מתוך PR בנושא Concurrent Mode חשוב בהיבט הזה: > This was when we were more bullish about lazy fetching being a good idea some of the time (when combined with prefetching), as opposed to our latest thinking, which is that it's almost always a bad idea. האם lazy fetching הוא רעיון טוב או רע? אני לא רוצה להיכנס לדיון הזה (יש דיון מעניין בקישור). האם הטריגר למשיכת מידע משרת צריך להיות רינדור של קומפוננטה או איזשהו טריגר גדול יותר ברמת האפליקציה? שוב, דיון מעניין, לא להיום. מה שחשוב הוא שבדיון הזה צוות המפתחים של ריאקט נקט עמדה ברורה לצד אחד, והיום הם נוקטים עמדה ברורה לצד השני, כולל שינויים משמעותיים בפריימוורק שהופכים את אחת הגישות להרבה יותר קשה למימוש. עדיין מוקדם להגיד לאן הסיפור הזה הולך, אבל כן חשוב להשאיר בראש שדברים כאלה קורים וכשאנחנו בונים ארכיטקטורה ליישום שלנו לזכור שהאחריות עלינו, גם כשאנדרו קלארק משנה את דעתו.

ToCode
1 419
'my-cell': MyCell
  }
}
נעבור בצורה זריזה על הרעיונות החשובים שלמדתי מכתיבת הדוגמה: 1. כשאני בתוך קומפוננטה של "תא" ורוצה להעביר פוקוס הדרך היחידה שלי לצאת החוצה לקומפוננטה של הטבלה היא לשלוח אירוע. רק קומפוננטת הטבלה יכולה לקבל את האירוע ולהעביר את הפוקוס. 2. גם כשאני רוצה להעביר את הפוקוס מה td ל input שנמצא בתוכו יש לבצע את הקריאה מהקומפוננטה של הטבלה, כי היא זו שמגדירה את ה input שבתוך ה td. 3. אבל כשאני רוצה להעביר את הפוקוס מה input ל td שמכיל אותו - פה צריך כבר לקרוא ל focus מקומפוננטת ה"תא בטבלה" כי היא זאת שיצרה את ה td. בקיצור כל קומפוננטה ב Shadow DOM אחראית על האלמנטים שהיא יוצרת. עוד שתי נקודות שלמדתי מהדוגמה הזאת על ליט: 1. הגדרת קובץ CSS לכל העמוד לא משפיעה על הקומפוננטות של lit. כל קומפוננטה חייבת להגדיר את קובץ ה CSS בעצמה. מאוד מעייף כשרוצים להגדיר עיצוב גלובאלי באפליקציית lit. 2. מנגנון הטמפלייטס של ליט הוא מורכב והמורכבות הזאת לא תמיד עובדת לטובתנו. לדוגמה בדרך הגדרת התבניות הרגילה שלהם אי אפשר להשתמש במשתנה כדי לקבוע מה יהיה ה tag בתוך התבנית. אפשר לעקוף את זה אם משתמשים בפונקציה אחרת שלהם כדי ליצור את התבנית (לשתי הפונקציות קוראים באותו שם html), אבל אז מאבדים את הריאקטיביות.

ToCode
1 419
ניהול פוקוס ב Shadow DOM אחד הקשיים המרכזיים שהיו לי בעבודה עם ליט היה להיפרד מהרעיון שכל הדברים חיים באותו עולם. למעשה כבר הרבה שנים שפריימוורקים של ווב מנסים לקחת את "העולם האחד" של הדפדפן ולבנות בתוכו קירות. בעבודה עם ליט הקיר כבר שם - והוא ה Shadow DOM, ולכן העברת המידע בין קומפוננטות חייבת לקרות רק דרך מנגנון האירועים. בדוגמה הפעם בניתי טבלה שמורכבת מקומפוננטה אחת בשביל התא וקומפוננטה אחרת בשביל הטבלה, ורציתי שלחיצה על החצים כשאני בפוקוס על אחד התאים תשנה את הפוקוס לתא שמימין, משמאל מעל או מתחת (לפי החץ שנלחץ). אפשר למצוא את הקוד והתוצאה בסטאקבליץ כאן: https://stackblitz.com/edit/vitejs-vite-r29prn?file=src%2Fmy-element.ts וזה הקוד בהדבקה:
import { LitElement, css, html, unsafeCSS } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import litLogo from './assets/lit.svg'
import viteLogo from '/vite.svg'
import _ from 'lodash';
import style from "./index.css?inline";

@customElement('my-cell')
export class MyCell extends LitElement {
  static styles = unsafeCSS(style);
  static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};


  handleKeypress(ev: KeyboardEvent) {
    if (ev.key === "Enter") {
      if (this.shadowRoot?.activeElement === this.shadowRoot?.querySelector('td')) {
        // td is in focus
        this.dispatchEvent(new Event('focusinside', {composed: true, bubbles: true}))
      } else {
        this.shadowRoot?.querySelector('td').focus();
      };
    } else if (ev.key === "ArrowRight") {
      this.dispatchEvent(new Event('focusright', {composed: true, bubbles: true}))
    } else if (ev.key === "ArrowLeft") {
      this.dispatchEvent(new Event('focusleft', {composed: true, bubbles: true}))
    } else if (ev.key === "ArrowUp") {
      this.dispatchEvent(new Event('focusup', {composed: true, bubbles: true}))
    } else if (ev.key === "ArrowDown") {
      this.dispatchEvent(new Event('focusdown', {composed: true, bubbles: true}))
    }
  }

  takeFocus(ev: KeyboardEvent) {
    if (ev.key === "Enter") {
      this.shadowRoot?.querySelector('td')?.focus();
    }
  }

  render() {
    return html\<td
      tabindex="1"
      @keyup=${this.handleKeypress}
      class="border-dashed border border-1 border-gray-500 focus:border-solid focus:border-green-200 focus:border-2"
    >
      <slot></slot>
    </td>
    \
  }
}

@customElement('my-table')
export class MyTable extends LitElement {
  static styles = unsafeCSS(style);

  @property({ type: Number })
  rows = 4

  @property({ type: Number })
  columns = 2

  focusUp(e: any) {
    const me = e.target;
    const tr = me.parentNode;
    const index = Array.prototype.indexOf.call(tr.children, me);
    tr.previousElementSibling.children[index].focus();
  }

  focusDown(e: any) {
    const me = e.target;
    const tr = me.parentNode;
    const index = Array.prototype.indexOf.call(tr.children, me);
    tr.nextElementSibling.children[index].focus();
  }

  render() {
    return html\
      <table class="bg-orange border-collapse"
        @focusinside=${(e: any) => e.target.firstElementChild.focus()}
        @focusright=${(e: any) => e.target.nextElementSibling.focus()}
        @focusleft=${(e: any) => e.target.previousElementSibling.focus()}
        @focusup=${this.focusUp}
        @focusdown=${this.focusDown}
      >
        <tbody>
          ${_.range(this.rows).map((i) => (
            html\
            <tr>
              ${_.range(this.columns).map((j) => (
              html\<my-cell>
                <input
                type="text"
                value="${\${i}, ${j}\}"
                class="outline-none bg-transparent border-none border-2"
              /></my-cell>\
              ))}
            </tr>
          \
          ))}
        </tbody>
      </table>
    \
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'my-table': MyTable

ToCode
1 419
הפונקציות toReversed ו toSorted ב JavaScript אני תמיד אוהב כששפת תכנות לוקחת עוד צעד בכיוון הפונקציונאלי, וזה יהיה הסיפור היום עם שתי פונקציות חדשות ב JavaScript - הפונקציות toReversed ו toSorted, הגירסאות הפונקציונאליות של reverse ו sort. בעוד שהפונקציות המקוריות שינו את מבנה הנתונים, הגירסאות החדשות שלהן מחזירות לנו עותק של מבנה הנתונים ממוין או הפוך. הפונקציה reverse של JavaScript מופעלת על מערך והופכת את האיברים בו:
> const x = [1, 2, 3, 4];
undefined
> x.reverse()
[ 4, 3, 2, 1 ]
הגירסה החדשה שלה toReversed מחזירה עותק חדש של המערך עם האיברים הפוכים:
> const x = [1, 2, 3, 4]
undefined
> x.toReversed()
[ 4, 3, 2, 1 ]
> x
[ 1, 2, 3, 4 ]
נשים לב שכשהמערך מקונן האלמנטים הפנימיים לא מועתקים, לכן:
> const x = [{a: 10}, {a: 10}, {a: 20}, {a: 30}]
undefined
> x.toReversed()
[ { a: 30 }, { a: 20 }, { a: 10 }, { a: 10 } ]
> x.toReversed()[0].a = 40
40
> x
[ { a: 10 }, { a: 10 }, { a: 20 }, { a: 40 } ]
אם במקום היפוך אנחנו רוצים למיין נוכל לקרוא ל toSorted, שהיא הגירסה הפונקציונאלית של sort. כמו sort גם toSorted יכולה לקבל פונקציית מיון או למיין לפי סדר מילוני:
> const values = [1, 10, 21, 2];
undefined

> values.toSorted()
[ 1, 10, 2, 21 ]

> values.toSorted((a, b) => a - b)
[ 1, 2, 10, 21 ]
אפשר לחשוב על שתיהן בתור גירסה מהירה וברורה יותר של array.slice().reverse() ו array.slice().sort:
> x.slice().reverse()
[ 9, 1, 2, 10 ]
> x
[ 10, 2, 1, 9 ]

> x.slice().sort()
[ 1, 10, 2, 9 ]
> x
[ 10, 2, 1, 9 ]

ToCode
1 419
חמישה טיפים ל Pair Programmning יעיל יותר תכנות בצוות יכול לעזור לנו לכתוב קוד טוב יותר ומהר יותר, אבל גם יכול בקלות להשתבש. המפתח להצלחה, אם כי קשה מאוד ליישום הוא בחירה טובה של השותפים לפרויקט. הנה כמה טיפים שעוזרים לי לזהות אנשים שכדאי לכתוב איתם קוד, ולחלופין אנשים שעדיף לכל הצדדים שנעבוד בנפרד: 1. בחרו חבר או חברה עם רקע שונה משלכם. המטרה של Pair Programming היא לא לכתוב קוד כמה שיותר מהר אלא לדבר על הדברים המשמעותיים בתהליך הפיתוח. ככל שיש פער ברקע שלכם אפשר יהיה לקבל נקודת מבט נוספת ומעניינת על הארכיטקטורה. 2. בחרו טכנולוגיות אותן לפחות אחד הצדדים מבין או מבינה היטב. המטרה של Pair Programming היא הדיון, אז אנחנו רוצים לצמצם זמני Debug וזמני חיפוש באינטרנט. 3. עשו שיעורי בית - לא צריך לכתוב יחד את כל הקוד. נסו להחליט יחד על הארכיטקטורה, לממש יחד את החלקים הקשים והחשובים של הפרויקט, ואז להתקדם בנפרד בחלקים שלא דורשים מחשבה. 4. הקשיבו לצד השני גם (ובמיוחד) כשנשמע שהם אומרים שטויות - זוכרים שהתחלנו עם חבר או חברה עם רקע שונה משלנו? זה אומר שחלק מהדברים שנראים לנו מובנים מאליהם יהיו עבורם חדשים ומפתיעים, וחלק מהדברים שנראים להם מובנים מאליהם יהיו לנו חדשים ומפתיעים. אין לכם כרגע את הכלים להבדיל בין השטויות לדברי החוכמה של הצד השני כי הגעתם מעולמות שונים, ולכן היו מוכנים להקשיב ולנסות רעיונות. 5. גיט סטאש הוא חבר - חלק מהמשחק של לעבוד ביחד זה להיחשף לרעיונות חדשים שלא הייתם מנסים אם הייתם לבד. חלק מהרעיונות האלה טובים, חלק גרועים וחלק לא מספיק מלוטשים. אני משתדל לקבור מהר עם git restore את הרעיונות הגרועים, אבל את הלא מלוטשים להשאיר בצד עם git stash. יום אחד אולי יהיה זמן לחקור אותם שוב. או שלא. וזה בסדר. יש לכם טיפים נוספים שעבדו? אשמח לשמוע בתגובות פה או בטלגרם.

ToCode
1 419
עדכון אימג'ים אוטומטי עם Watchtower נכון, אנחנו לא אמורים להריץ מערכת פרודקשן עם Docker Compose. נכון, הוא לא כזה טוב בהוספת עוד קונטיינרים, בניהול הקונטיינרים שרצים ובכל הדברים שקוברנטס ודומיו יודעים לעשות. אבל אי אפשר להתווכח עם הפשטות שלו ולפעמים כשהפרויקט קטן דוקר קומפוז כן יכול לתת פיתרון סביר, גם אם זמני. ואם אנחנו כבר מריצים מערכת עם Docker Compose, מדי פעם אנחנו צריכים גם להעלות גירסה חדשה של אחד האימג'ים. בדיוק בשביל זה נוצר Watchtower. ל Watchtower יש תפקיד אחד פשוט ושם הקסם שלו - פעם ב X זמן (כמה שתחליטו) הוא מתחבר ל Registry, מחפש גירסאות חדשות של האימג'ים ואם הוא מוצא הוא מוריד את הגירסאות החדשות ומעלה מחדש קונטיינרים עם אותן גירסאות חדשות. זה אומר שמספיק לדחוף אימג' ל Registry בשביל לעדכן מכונה רצה וזה כבר טוב. איך זה עובד? יחסית פשוט. Watchtower הוא סרביס שאנחנו מוסיפים ל docker-compose.yml, אפשר בנוסף לסרביס שלנו או בקומפוז אחר לגמרי:
version: "3"
services:
  cavo:
    image: ynonp/myapp:latest
    ports:
      - "443:3443"
      - "80:3080"
  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 30
כן צריך לשים לב ל-3 נקודות שאפשר ליפול בהן: 1. אם האימג' שמור ברגיסטרי פרטי אז צריך להעביר פנימה לתוך הקונטיינר של watchtower את פרטי ההתחברות, בדרך כלל בצורת מיפוי הקובץ config.json של דוקר פנימה לתוך הקונטיינר. 2. האינטרוול קובע כל כמה זמן צריך לבדוק אם קיימת גירסה חדשה של האימג'. ברירת המחדל היא פעם ביום אז בשביל מנגנון של דיפלוימנט ב CI/CD כדאי להוריד את זה. המספר 30 בדוגמה מציין 30 שניות. 3. אם לא כתבתם אחרת ה watchtower יעדכן את כל האימג'ים בכל הקונטיינרים שרצים על המכונה. אפשר לסנן קונטיינרים אם נעביר אחרי האינטרוול את שם הקונטיינר שאנחנו רוצים לעדכן (שימו לב שזה שם הקונטיינר לא שם הסרביס בקומפוז), או שיטה יותר פשוטה לדעתי היא להגדיר label על כל קונטיינר שצריך עדכון. נשים לב שאם יש לנו מספר פרויקטים שונים עם קבצי docker-compose שונים אז עדיין watchtower של אחד עשוי לשדרג קונטיינרים של הפרויקט השני. סך הכל שילוב שלושת הסעיפים מביא אותנו ל docker-compose.yml שנראה בערך כך:
services:
  db:
    image: postgres:16.3
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DBPASSWORD}

  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/ynon/.docker/config.json:/config.json
    command: --interval 30 --label-enable

  web:
    image: my.private.registry.com/webapp:latest
    labels:
      com.centurylinklabs.watchtower.enable: true
    environment:
      DATABASE_PASSWORD: ${DBPASSWORD}
      DATABASE_HOST: db
    ports:
      - "3000:3000"
    depends_on:
      - db

ToCode
1 419
עכשיו הפעילו מחדש את השרת בשני חלונות - בחלון אחד נפעיל ./bin/vite dev ובחלון שני ./bin/rails s. אם הכל הלך כמו שצריך תוכלו להיכנס ל localhost:3000 ולראות את הקומפוננטה ובנוסף תוכלו לשנות את קוד הקומפוננטה, לשמור ולראות את העמוד בדפדפן מתעדכן. בשביל לרנדר קומפוננטה אחרת לפי העמוד אני מעדיף לכתוב קצת קוד JavaScript שיסתכל בעמוד, יטען קומפוננטות בצורה דינמית לפי data attribute של אלמנטים ויאתחל קומפוננטות אלה. אני משנה את שם הקובץ הראשי מ application.js ל application.jsx וכותב בו את התוכן הבא:
// file: app/frontend/entrypoints/application.jsx

import React from 'react';
import { createRoot } from 'react-dom/client';

async function renderAll() {
  const reactEntrypoints = document.querySelectorAll('.react');
  for (const el of reactEntrypoints) {
    const componentName = el.dataset.component;
    const Component = (await import(\../components/${componentName}.tsx\)).default;

    const root = createRoot(el);

    const dataprops = el.dataset.props;
    const props = dataprops ? JSON.parse(dataprops) : {};

    root.render(<Component {...props} />)
  }
}
  
renderAll();
גם את קוד הקומפוננטה אני מעדכן כי אפשר להוריד משם את ה render (הוא קורה בקוד הראשי) ולכן הקובץ Home.tsx מכיל עכשיו רק את זה:
import React from 'react';

export default function Home({text}: {
  text: string
}) {
  return <div>
      <h1>{text}</h1>
      <a href="/about">About Page</a>
    </div>
}
ולסיום אני מעדכן את ה View בקובץ app/views/home/index.html.erb כדי ליצור את הקומפוננטה:
<div
  class="react"
  data-component="Home"
  data-props='<%= {text: "hello"}.to_json %>'
></div>
שימו לב שה Properties לקומפוננטה כתובים כאן ב View, זה אומר שנוכל בקלות להעביר אותם מה Controller. רק בשביל המשחק אני מוסיף דף נוסף לאתר. בקובץ app/controllers./home_controller.rb אני מוסיף עוד פונקציה:
class HomeController < ApplicationController
  def index
  end

  def about    
  end
end
בתיקיית frontend/components אני יוצר קומפוננטה נוספת בשם About.tsx:
// file: app/frontend/components/About.tsx

import React from 'react';

export default function About() {
  return <p>About Us</p>
}
ובקובץ app/views/home/about.html.erb אני כותב את פרטי הקומפוננטה:
<div
  class="react"
  data-component="About"
></div>
ולסיום בקובץ config/routes.rb אני מוסיף את הנתיב:
Rails.application.routes.draw do
  root to: 'home#index'
  get '/about', to: 'home#about'
end
עכשיו אפשר לעבור בין שני הדפים ולראות שכל כניסה לדף מציגה את הקומפוננטה שמתאימה לו. עוד ספריות מומלצות יש עוד מספר ספריות שלפעמים אני משלב שהופכות את החיבור בין ריאקט לריילס לאפילו יותר מוצלח: 1. הספריה js-from-routes מחברת בין ה Routes שמוגדרים בריילס בתור פונקציות ל JavaScript. 2. הספריה typesfromserializers יודעת לייצר ממשקי טייפסקריפט ישירות מתוך Serializers. סריאקלייזרים למי שלא מכיר זה מנגנון ריילסי שקובע איך מודל יהפוך ל JSON. עם הספריה הזאת יש לנו אוטומטית טיפוסי טייפסקריפט שמתאימים לכל ה JSON-ים שאנחנו שולחים מריילס (בין אם בתור props לקומפוננטות או דרך ה API).

ToCode
1 419
מבנה פרויקט Rails, React ו TypeScript אנחנו ב 2024, ריילס עדיין שווה את המאמץ וריאקט הפכה לספריית ה UI הדיפולטית של מפתחים רבים, כמובן בשילוב טייפסקריפט. יש המון אפשרויות לשילוב שלושת הטכנולוגיות האלה יחד ואני רוצה להראות כאן שיטה אחת שעבדה בשבילי די טוב. ריילס אני מתחיל ביצירת פרויקט ריילס 7 חדש עם:
$ rails new  --skip-javascript .
למרות שיש בריילס מנגנון לעבודה עם JavaScript (אפילו שניים), אני מעדיף את vite ולכן אני מתקין את הג'ם vite-ruby לפי הוראות ההתקנה שלהם. מתוך תיקיית הפרויקט אני כותב:
bundle add 'vite_rails'
bundle exec vite install
ואז אפשר לנסות להפעיל את שרת הפיתוח:
$ ./bin/vite dev
ואצלי מופיע הפלט הבא:
The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

  VITE v5.2.13  ready in 242 ms

  ➜  Local:   http://localhost:3036/vite-dev/
  ➜  press h + enter to show help
^C
אני עוצר אותו עם Ctrl+C כדי לתקן כמה קונפיגורציות. מריצים:
$ yarn add vite-plugin-rails typescript react react-dom @vitejs/plugin-react @types/react @types/react-dom
ואז משנים את הקובץ vite.config.ts לתוכן הבא:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import ViteRails from 'vite-plugin-rails'

export default defineConfig({
  plugins: [
    react(),
    ViteRails(),
  ],
})
ממשיכים בשורת הפקודה ויוצרים את הקונטרולר הראשון:
$ ./bin/rails g controller HomeController index
ואז בקובץ config/routes.rb אני מוסיף הפניה אליו כדי שנוכל לראות אותו בדפדפן:
Rails.application.routes.draw do
  root to: 'home#index'
end
מפעילים את השרת עם ./bin/rails s, נכנסים ל localhost:3000 ורואים את הדף שיצרנו. ריאקט קובץ ה JavaScript שנטען כשהיישום שלנו עולה הוא app/javascript/entrypoints/application.js. אני יוצר תיקייה חדשה בשם app/frontend/components ובתוכה קובץ חדש בשם Home.tsx. תוכן הקובץ הוא:
import React from 'react';
import { createRoot } from 'react-dom/client';

function Home({text}: {
  text: string
}) {
  return <h1>{text}</h1>
}

const main = document.querySelector('main')!;
const root = createRoot(main);

root.render(<Home text="Hello World" />);
הקובץ מגדיר את הקומפוננטה הראשונה שלנו וגם מרנדר אותה למסך. זה עדיין לא מספיק טוב כי זה אומר שכל עמוד שניכנס אליו יציג את אותה קומפוננטה. תכף נתקן את זה. אבל קודם בואו נראה שרואים את הקומפוננטה על המסך. לפני שנוכל להתקדם יש לעדכן את הקובץ app/views/layouts/application.html כך שיוכל לרענן דפי vite וגם יכיל אלמנט main אליו נרנדר את הקומפוננטה שיצרנו. עדכנו את תוכן הקובץ לקוד הבא:
<!DOCTYPE html>
<html>
  <head>
    <title>RailsReactTypescriptDemo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">

    <% if Rails.env.development? %>
    <script type="module">
      import RefreshRuntime from 'http://localhost:3036/vite-dev/@react-refresh'
      RefreshRuntime.injectIntoGlobalHook(window)
      window.$RefreshReg$ = () => {}
      window.$RefreshSig$ = () => (type) => type
      window.__vite_plugin_react_preamble_installed__ = true
    </script>
    <% end %>

    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application" %>
    <%= vite_client_tag %>
    <%= vite_javascript_tag 'application' %>
    <!--
      If using a TypeScript entrypoint file:
        vite_typescript_tag 'application'

      If using a .jsx or .tsx entrypoint, add the extension:
        vite_javascript_tag 'application.jsx'

      Visit the guide for more information: https://vite-ruby.netlify.app/guide/rails
    -->

  </head>

  <body>
    <main />
    <%= yield %>

  </body>
</html>