הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

חידת Vue משתנים ריאקטיביים

03/01/2025

נתון קוד Vue הבא:

<script setup>
import { ref } from 'vue'
const init = {count: 0}
const item = ref(init)

function inc() { item.value.count++ }
function reset() { item.value = init }
</script>

<template>
  <p>{{ item.count }}</p>
  <button @click="inc">+1</button>
  <button @click="reset">Reset</button>
</template>

משתמש לוחץ כמה פעמים על כפתור הפלוס ואז על כפתור ה Reset. האם הערך בתיבה מתאפס? למה?

וכן הכנתי לכם את זה כבר בלייב שתוכלו לבדוק כאן: דוגמת לייב

המשך קריאה

מבחן ה ack

02/01/2025

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

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

טריק אחד של התחשבות שאני אוהב ליישם הוא מבחן ה ack, וזה אומר שתהיה לי מילה אחת מרכזית בפיצ'ר שתחזור על עצמה בכל המקומות שהפיצ'ר הזה נוגע בהם וכך אפשר יהיה לקבל תמונת מבט על של הפיצ'ר גם כשמגיעים רק לתקן באג או לבדוק משהו.

לדוגמה אם אני בונה פיצ'ר של הפקת תעודות לקורסים אז אני יכול להתחיל עם מילה שקשורה לתעודות כמו certificate ולהמשיך איתה בשמות של מחלקות או פונקציות, וככה נקבל שמות כמו:

  1. CertificatesController

  2. CertificatesListView

  3. isCertificateReady

  4. certificateStatus

  5. UserInfoForCertificate

האנטי תבנית כאן היא להיות יצירתיים ולהשתמש באוצר מילים מגוון מדי, למשל אם אני כותב פונקציה שתבדוק אם משתמש סיים קורס אני לא ארצה לקרוא לה isGraduated (למרות שבמקומות אחרים זה אולי שם טוב) ועדיף לבחור משהו שמכיל את מילת המפתח שלי למשל isCertificateEligible. אל תפחדו להיעזר ב Chat GPT כדי למצוא שמות מתאימים למילת מפתח שבחרתם.

אני עוד אתגעגע ל Facade

01/01/2025

תבנית העיצוב Facade היא אוביקט שנמצא לפני ספריה אחרת ומסתיר התנהגות מסובכת או מורכבת יותר. המטרה המרכזית של Facade היא לאפשר לנו לכתוב קוד יותר קצר או קריא, או לתת פיתרון ל Use Case מסוים שבו לא צריך את כל המורכבות של ספריה אחרת.

לדוגמה בפייתון הספריה requests עוטפת את ממשק התקשורת של השפה ומספקת דרך קלה יותר למשוך מידע מהרשת. יש לה גם יותר פיצ'רים כמובן. ביקשתי מהבוט תוכניות דוגמה עם ובלי requests וזה מה שקיבלתי, תוכנית ראשונה בלי requests רק פייתון:

import urllib.request
import json

def get_data_using_urllib():
    url = "https://api.example.com/data"

    # 1. Create a request object (so you can set headers if you need)
    req = urllib.request.Request(url, headers={"Accept": "application/json"})

    try:
        # 2. Open the URL
        with urllib.request.urlopen(req) as response:
            # 3. Check the status code
            if response.status == 200:
                # 4. Read and parse JSON data
                raw_data = response.read()
                data = json.loads(raw_data)
                print("Data fetched using urllib:", data)
            else:
                print(f"Error: status code = {response.status}")
    except Exception as e:
        print("An error occurred:", e)

if __name__ == "__main__":
    get_data_using_urllib()

תוכנית שניה עם requests:

import requests

def get_data_using_requests():
    url = "https://api.example.com/data"

    try:
        # 1. Make a GET request
        response = requests.get(url)

        # 2. Raise an exception if the status is not 2xx
        response.raise_for_status()

        # 3. Parse JSON in one step
        data = response.json()
        print("Data fetched using Requests:", data)

    except requests.exceptions.RequestException as e:
        print("An error occurred:", e)

if __name__ == "__main__":
    get_data_using_requests()

אחלה פסאדה נכון? אז למה שנתגעגע אליה? אני חושב שזו אשמת אותו בוט בדיוק שיצר את שתי התוכניות. אם פעם ההתלבטות לגבי כתיבת אבסטרקציה חדשה התרכז בעלות של פיתוח אותה אבסטרקציה (תחזוקה, שידרוג כל פעם שהתשתית שהפסאדה בנויה עליה מתעדכנת), בגלל Chat GPT אנחנו מתלבטים עכשיו גם לגבי הערך. כן ברור שעם ספריות קיימות כמו requests אין לו בעיה לכתוב קוד אבל אם אני כותב אבסטרקציה חדשה ה AI עדיין לא מכיר אותה, ולכן לא יוכל לכתוב איתה קוד. עכשיו נסו לדמיין שאתם צריכים להוציא בקשת רשת בפייתון, אופציה אחת היא לקחת את הקוד העובד ש Chat GPT נתן ואופציה שניה היא לשלב ספריה שמבטיחה לעשות את אותו דבר בפחות שורות. הבעיה שכש Chat GPT כותב את הקוד, כמות השורות היא כבר לא פקטור.

בראייה קדימה, נדמיין שנתיים קדימה כשכל הקוד שנקבל מ AI יעבוד, יהיה מאוד קשה לשכנע אנשים להשתמש בממשקים "קלים" במקום בממשקים ש Chat GPT יודע לייצר.

ריאקט מול ויו - שימו לב לשינויי סטייט

31/12/2024

גם בריאקט וגם ב Vue יש תמיכה בערכים מחושבים, אבל הרזולוציה שונה ואנחנו צריכים לשים לב לזה כשכותבים קוד.

בריאקט הקומפוננטה הבאה מגדירה שני ערכים ושני ערכים מחושבים:

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  const twoX = x * 2;
  const twoY = y * 2;

  return (
    <div className="App">
      <label>
        X:
        <input
          type="number"
          value={x}
          onChange={(e) => setX(Number(e.target.value))}
        />
        <span>x = {x}; </span>
        <span>2x = {twoX}</span>
      </label>
      <label>
        Y:
        <input
          type="number"
          value={y}
          onChange={(e) => setY(Number(e.target.value))}
        />
        <span>y = {y}; </span>
        <span>2y = {twoY}</span>
      </label>
    </div>
  );
}

אבל לא היינו כותבים קוד כזה בעולם האמיתי. הרזולוציה לא נכונה. בדוגמת הקוד גם כש x משתנה וגם כש y משתנה יש לחשב מחדש את שני הערכים המחושבים twoX ו twoY, בגלל שרזולוציית שינויים בריאקט היא קומפוננטה. בעולם האמיתי הקומפוננטה הזאת היתה מחולקת לשתי קומפוננטות, גם בגלל כפל הקוד וגם ובעיקר בגלל ה State - לקומפוננטה יש מספר אזורים שכל אחד מהם מושפע באופן עצמאי מסטייט אחר, וזה סימן ברור שמשהו בבחירת הקומפוננטה שלנו לא נכון.

ב Vue הסיפור קצת יותר מורכב אבל גם יותר קל למשתמשים. זאת הקומפוננטה:

<script setup>
import {ref, computed} from 'vue';
const x = ref(0);
const y = ref(0);
const twoX = computed(() => x.value * 2);
const twoY = computed(() => y.value * 2);
</script>

<template>
  <div class="App">
      <label>
        X:
        <input
          type="number"
          v-model="x"
        />
        <span>x = {{x}}; </span>
        <span>2x = {{twoX}}</span>
      </label>
      <label>
        Y:
        <input
          type="number"
          v-model="y"

        />
        <span>y = {{y}}; </span>
        <span>2y = {{twoY}}</span>
      </label>
    </div>
</template>

הקוד זהה אבל ההתנהגות שונה - שינוי ב x גורם לחישוב מחדש רק של twoX ושינוי ב y גורם לחישוב מחדש רק של twoY, כלומר רזולוציית השינוי היא לפי המשתנה והערכים שמחושבים ממנו. זה אומר שב vue יהיו לנו פחות בעיות ביצועים כתוצאה משינוי שיצא משליטה, אבל מצד שני יותר קשה לנו להבין את ההשפעה האמיתית של כל שינוי כי בשביל לראות מה מושפע מ x צריך לעקוב אחרי ה computed ולהסתכל על ה render function שנוצרה ואיזה חלקים ממנה מושפעים מכל ref.

בסוף בשני המקרים יהיה הגיוני לחלק אחרת לקומפוננטות לפי מבנה ה State. אפשר להגיד ש vue יותר מתאמץ בשביל שנוכל "לחיות" עם הטעות בלי לשלם בביצועים וריאקט דורש תשומת לב גבוהה יותר לפרטים בבניית המערכת. שמעתי כבר את הטיעון שבגלל זה קוד ריאקט באופן כללי הוא טוב יותר, כי אנשים יותר מתאמצים לכתוב נכון. אני חייב להודות שמהניסיון שלי הרבה פעמים זה לא ממש עוזר וקוד ריאקט של מערכות גדולות גם לא מחולק נכון לקומפוננטות וכתוצאה מזה גם סובל מבעיות ביצועים.

האם להשתמש בטרנספורמציות על מידע ב echarts?

30/12/2024

אחד הפיצ'רים החמודים של echarts הוא היכולת להריץ Data Pipeline בתוך קוד יצירת הגרף. אני מודה שאני עדיין מתלבט מתי להשתמש בזה ומתי להריץ את המניפולציה על המידע מראש, במיוחד כשמשלבים את echarts עם ספריית פרונטאנד כמו ריאקט או ויו. בכל מקרה ובינתיים בשביל המשחק בואו נראה איך זה עובד. שימו לב להגדרת המידע הבאה:

  dataset: [
    {
      // 1) Raw data from your JSON array
      id: 'raw',
      source: data
    },
    {
      // 2) Sort descending by SUMACCIDEN
      id: 'sorted',
      fromDatasetId: 'raw',
      transform: {
        type: 'sort',
        config: {
          dimension: 'SUMACCIDEN',  // the field to sort by
          order: 'desc'
        }
      }
    },
  ],

אני מתחיל עם מערך של אוביקטים בשם data, ובמקום למיין אותו מראש בקוד אני משתמש בטרנספורמציה של echarts כדי להסתכל על המידע הממוין. היתרון הוא שאפשר לייצר טרנספורמציות שונות ולהציג גרפים אחרים לכל טרנספורמציה, או לחבר את הטרנספורמציות למערכת ניהול האירועים של echarts.

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

האלטרנטיבה היא להשתמש ב JavaScript פשוט ואז יש לנו גם הרבה יותר גמישות, לדוגמה הקוד הבא מארגן את הנתונים ומאחד שורות כשיש מספר שורות לאותה עיר, וגם לוקח את עשרת הערים עם הכי הרבה תאונות:

const sortedData = _.chain(data)
  .filter(item => item.city && item.SUMACCIDEN)
  .groupBy('city')
  .mapValues(v => _.sumBy(v, item => item.SUMACCIDEN))
  .toPairs()
  .orderBy(p => p[1], 'desc')
  .value()
  .slice(0, 10)

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

דוגמה? בטח. הקוד הבא יציג לכם את עשר הערים עם הכי הרבה תאונות דרכים בספטמבר 2024 ומספר תאונות הדרכים בכל עיר בגרף עמודות:

<script setup>
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
import _ from 'lodash';
import {
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent,
  DatasetComponent,
} from 'echarts/components';
import VChart from 'vue-echarts';
import { ref } from 'vue';

// Register only the components we need (modular import)
use([
  CanvasRenderer,
  BarChart,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
  GridComponent,
  DatasetComponent,
]);

// Fetch your JSON array of objects (converted from CSV)
const data = await fetch('https://assets.codepen.io/5217/data.json').then(res => res.json());
window.data = data;

const sortedData = _.chain(data)
  .filter(item => item.city && item.SUMACCIDEN)
  .groupBy('city')
  .mapValues(v => _.sumBy(v, item => item.SUMACCIDEN))
  .toPairs()
  .orderBy(p => p[1], 'desc')
  .value()
  .slice(0, 10)

const option = ref({
  title: {
    text: 'Top 10 Cities with Car Accidents',
    left: 'center'
  },
  tooltip: {
    trigger: 'axis'
  },
  // We use ECharts “dataset” + transforms for sorting and limiting to top 10
  dataset: {
    source: sortedData,
    id: 'sorted'
  },
  xAxis: {
    type: 'category'
  },
  yAxis: {
    name: 'Number of Accidents'
  },
  series: [
    {
      type: 'bar',
      datasetId: 'sorted',
   
      // Optional styling
      itemStyle: {
        color: '#5470c6'
      }
    }
  ]
});

</script>

<template>
  <!-- v-chart from vue-echarts -->
  <v-chart class="chart" :option="option" autoresize />
</template>

<style scoped>
.chart {
  height: 600px;
  width: 100%;
}
</style>

בשביל להריץ תצטרכו לשים אותו בתוך תוכנית vue ולהתקין את vue-echarts ו echarts.

נ.ב. הפוסט במקור פורסם עם טעות. הוא נערך והתוכנית כעת מדפיסה את הגרף הנכון.

שלוש סיבות לעבוד מקומית במיוחד בתור מתחילים

29/12/2024

לא מזמן ראיתי פוסט שהמליץ למתחילים לעבוד רק עם סביבות פיתוח אונליין, כדי לא להסתבך עם התקנות. אני מכיר טוב את הבעיה כולל מחברים שמלמדים תכנות: העבודה מ Google Colab, מ Code Sandbox או סטאקבליץ או אפילו מתוך replit הרבה יותר קלה לתלמידים ומורים מעדיפים לא להסתבך עם התקנות שאולי ייכשלו כי יש בעיה ספציפית למישהו על המחשב.

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

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

  2. זיהוי שגיאות לרוב עובד טוב יותר בסביבה מקומית - נכון ההתקנה הראשונית של סביבה מקומית יותר מורכבת, אבל אחרי שהכל מותקן חווית העבודה השוטפת ובמיוחד הודעות השגיאה נוטות להיות ברורות יותר. אפשר להגיד שזה דבר אחד פחות שיכול להישבר.

  3. המחקר קל יותר - בעבודה מקומית יותר קל לנו להיכנס לקוד כולל לקוד של ספריות חיצוניות או הגדרות הפרויקט כדי לראות מה כתוב שם ולהבין מה יש בפנים. היכולת לחקור איך דברים עובדים לרוב עוזרת ללמוד, גם אם היא לפעמים מאטה אותנו.

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

מה דעתכם? כשאתם לומדים כלי חדש מעדיפים להתקין מקומית, לפתח אונליין או להריץ קונטיינר?

שמות בעלי משמעות?

28/12/2024

רובי 3.4 הוסיפה תמיכה במילה שמורה חדשה - it. עכשיו אפשר לכתוב:

users
  .reject(&:admin?)
  .flat_map { find_teams(it) }
  .uniq

מעניין לשים לב שרק לפני 5 שנים יצאה גירסה 2.7 של רובי שהוסיפה את התמיכה ב _1, והיתה הרבה התלהבות סביב זה כי עד אז היינו כותבים:

users
  .reject(&:admin?)
  .flat_map { |user| find_teams(users) }
  .uniq

והחל מ 2019 אנחנו יכולים לכתוב:

users
  .reject(&:admin?)
  .flat_map { find_teams(_1) }
  .uniq

ופה המקום לחזור לפיסקה הראשונה ולשאול - מה בעצם קרה פה? למה צריך גם _1 (ואת החברים שלו _2 לפרמטר השני, _3 לשלישי ועד _9) וגם את it שמתנהג בדיוק כמו _1 שכבר קיים?

אבל אולי יותר נכון להסתכל על התוספת הזאת כמו עוד חלק בפאזל התרבותי של רובי. כן חלק מהמטרה שם היא ליצור שפה עם הרבה מילים, ושתהיה מילה שמתאימה לכל דבר. כשרואים _1 בתוך בלוק אפשר לתהות האם יש גם _2 ולמה הוא לא כתוב שם. כשרואים it ברור שיש רק פרמטר אחד, ועבור אותם מקרים המילה החדשה שיפרה קצת את הקריאות של הקוד. ומה עם "צריכה להיות רק דרך אחת ברורה לעשות דברים?" נו, זה בכלל משפה אחרת.

קבלת מידע ב Streaming היא דווקא די פשוטה

27/12/2024

ממשקי Chat מאוד אוהבים להשתמש ב Streaming כי ל AI לוקח המון זמן לייצר את התשובה. בצד הלקוח משיכת מידע משרת HTTP ב Streaming התבררה כמשימה די פשוטה אני מדביק פה את הקוד ואחריו ההסבר:

export default async function* sendQuestion(questionText: string) {
  const res = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'llama3.2',
      prompt: questionText,
      stream: true,
    }),
  })

  if (!res.ok) throw new Error('Failed to connect to Ollama server')

  const reader = res.body?.getReader()
  if (!reader) throw new Error('Failed to initialize stream reader')

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    // Decode the stream data
    const chunk = new TextDecoder().decode(value)            
    const json = JSON.parse(chunk)
    yield json
  }
}

הקוד פונה לשרת של Ollama שרץ אצלי מקומית על המחשב ושולח לו שאלה. הפרמטר stream מקבל ערך אמת אבל זה לא הכרחי כי זו ברירת המחדל של פרמטר זה. אם התשובה היתה מגיעה בתור אוביקט אחד הייתי מפעיל את res.json() או res.text() כדי לקבל אותה, אבל בגלל שהתשובה מגיעה בתור זרם של אוביקטים אני קורא ל getReader על ה body, כדי לקבל ממשק קריאה. ה body הוא בעצם ReadableStream ואפשר לקרוא עליו בהרחבה בדף התיעוד:

https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/getReader

אחרי שלקחתי את ה Reader מתחיל החלק המעייף - עלינו לקרוא מה Reader את המידע חלק אחרי חלק ולפענח כל פעם את החלק הבא בתור אוביקט JSON. בקוד הזה אני מניח שכל Chunk יכיל אוביקט JSON שלם, למרות שבעולם האמיתי ייתכן ויידרש טיפול יותר פרטני כשפיענוח של Chunk נכשל כי אולי זה חלק שמחזיק רק חלק מאוביקט ה JSON וההמשך שלו הוא ב Chunk הבא. שימו לב לשימוש ב TextDecoder שמונע בעיה דומה עבור פיענוח הטקסט, כלומר ה decode של TextDecoderיודע לחתוך את הביטים העודפים אם יש ולהדביק אותם להתחלה של ה Chunk הבא כדי שיוכל להפוך ביטים לטקסט.

הפונקציה כולה היא Generator ואפשר להשתמש בה למשל מתוך קומפוננטת vue באופן הבא:

<script setup lang="ts">
import { ref } from 'vue'
import streamChatResponse from '../streamChatResponse';
const question = ref('')
const response = ref('')
const isLoading = ref(false)
const error = ref<string | null>(null)

async function sendQuestion() {
  for await (const chunk of streamChatResponse(question.value)) {
    response.value += chunk.response;
  }
  question.value = '';
}

</script>

<template>
  <div class="chat-container">
    <form @submit.prevent="sendQuestion" class="question-form">
      <textarea
        v-model="question"
        placeholder="Ask a question..."
        :disabled="isLoading"
        class="question-input"
      />
      <button 
        type="submit" 
        :disabled="isLoading || !question.trim()"
        class="submit-button"
      >
        {{ isLoading ? 'Thinking...' : 'Send' }}
      </button>
    </form>

    <div v-if="error" class="error-message">
      {{ error }}
    </div>

    <div v-if="response" class="response-container">
      <div class="response-text">{{ response }}</div>
    </div>
  </div>
</template>

<style scoped>
.chat-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 1rem;
}

.question-form {
  display: flex;
  gap: 1rem;
  margin-bottom: 1rem;
}

.question-input {
  flex: 1;
  min-height: 80px;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  resize: vertical;
}

.submit-button {
  padding: 0.5rem 1rem;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.submit-button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.error-message {
  color: #dc3545;
  margin-bottom: 1rem;
}

.response-container {
  padding: 1rem;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.response-text {
  white-space: pre-wrap;
}
</style>

הכלי הכי טוב

26/12/2024

בשביל לכתוב יישום ריאקט גדול בלי באגים ולהצליח להתמודד עם דרישות חדשות שמגיעות לאורך זמן בטוח צריך להבין מה זה State Management, צריך להחליט אם רוצים להשתמש ב Context כדי לשמור סטייט, אם מסתפקים בספריית ניהול סטייט קטנה כמו Valtio או שהולכים על כל הקופה ומשלבים רידאקס. אבל בתחילת הדרך או כשהיישום קטן הכלים האלה יכולים מאוד לבלבל.

וכאן השאלה - עד כמה צריך להתאמץ להבין איך רידאקס עובד כשאנחנו רק לומדים ריאקט? עד כמה חשוב להבין איך GraphQL עובד כשאנחנו רק מתחילים ללמוד על פיתוח קוד צד שרת? עד כמה חשוב "להתרגל" לשיטת עבודה מסוימת רק בגלל שהיא Best Practice ותעזור לנו בעתיד?

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

הכלי הכי טוב הוא כלי שפותר בעיה שעכשיו יש לך.

דוגמת קוד: וידוא קלט בשרת עם yup

25/12/2024

לפני כמה ימים הראיתי כאן איך לבדוק קלט בטופס צד לקוח עם yup. ראינו ש yup מגיע עם המון יכולות אבל החיבור שלו לממשק דורש עבודה וברוב המקרים אפשר לקבל תוצאה טובה יותר דרך הכלים המובנים ב HTML. מצד שני בעבודת צד שרת אנחנו מגלים כמה yup יעיל וגם המבנה האסינכרוני שלו נראה מאוד הגיוני, כי ממילא כל ה APIs ב node הם אסינכרוניים. שימו לב לקוד הבא לצד שרת עבור REST API עם Hono:

app.post('/signup', async (c) => {
  const body = await c.req.parseBody()
  try {
    await signupUserSchema.validate(body);
    // create the user and redirect
    return c.json({ ok: true });
  } catch (err) {
    throw new HTTPException(401, {message: String(err)})
  }
});

הקוד לוקח נתונים שהגיעו מבקשת POST לטופס ומוודא שהם תקינים תוך שימוש בסכימה של yup. המבנה הזה טוב כי הוא מפריד בין "מה שצריך לעשות" ל"איך צריך לעשות את זה", כלומר ברור כשמסתכלים על הקוד שיש פה וולידציה, ברור מה קורה אם הוולידציה מצליחה או אם היא נכשלת, אבל הוולידציה עצמה מבוססת על סכימה שמוגדרת במקום אחר. אם בעתיד נרצה לשנות שדות בטופס או את חוקי הוולידציה שלהם לא נצטרך לגעת בקוד של ה Endpoint.

ורק בשביל להשלים את התמונה זה קוד הסכימה מתוך הקובץ signupUserSchema:

import {object, string} from 'yup';

export default object().shape({
  email: string().required().email(),
  password: string().required().min(3),
});

וקוד הטופס:

<!DOCTYPE html>
<html>
  <body>
    <h1>Sign Up</h1>
    <form action="/signup" method="post">
      <label>
        Email
        <input type="email" name="email" required />
      </label>

      <label>
        Password
        <input type="password" name="password" required minlength="3" />
      </label>

      <input type="submit" />
    </form>
  </body>
</html>