ריאקט או Vue - הדגמה דרך הוק קצר

31/10/2024

הרבה אנשים מדברים על עקומת הלמידה של ריאקט בהשוואה ל Vue ויש בזה משהו. נכון לריאקט 18 (כלומר לפני ש Suspense יהיה דבר גדול ולפני use והבעיות שלו), האתגר הכי גדול עם ריאקט היה useEffect. בואו נראה דוגמה קצרה של אפקט בהשוואה בין שתי הספריות כדי להבין קצת את הרוח והייחודיות של כל אחת.

1. ריאקט: הקשבה לתנועת עכבר

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

import { useEffect, useState } from 'react'
import './App.css'

function useMousePosition() {
  const [position, setPosition] = useState([0, 0]);

  useEffect(() => {
    function handleMove(ev: MouseEvent) {
      setPosition([ev.clientX, ev.clientY]);
    }

    window.addEventListener('mousemove', handleMove);

    return () => {
      window.removeEventListener('mousemove', handleMove);
    }
  }, [])

  return position;
}

function App() {
  const position = useMousePosition();

  return (
    <h1>Position: {position[0]}, {position[1]}</h1>
  )
}

export default App

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

מבחינת קוד ה Hook יש פה קצת יותר אתגר בגלל המבנה של useState, useEffect והקשר ביניהם:

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

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

2. גירסת Vue

אותה דוגמה בגירסת Vue לא נראית יותר מדי שונה, וזו התחלה טובה:

<script setup lang="ts">

import { shallowRef, onMounted, onUnmounted } from 'vue'

function useMouse() {
  const position = shallowRef([0, 0])

  function update(event: MouseEvent) {
    position.value = [event.clientX, event.clientY];
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return position;
}

const position = useMouse();
</script>

<template>
  <h1>{{ position[0] }}, {{ position[1] }}</h1>
</template>

הקומפוננטה מגדירה את position בתור shallowRef במקום בתור useState, והעדכון שלו מתבצע על ידי השמה למאפיין .value. גם כאן אי אפשר לשנות רק תא אחד במערך וזה יכול להיות מבלבל. בניגוד לריאקט, ויו כולל עוד אופציות להגדרת משתנים ריאקטיביים כמו ref ו reactive שכן מאפשרות מעקב מקונן.

השינוי היותר גדול הוא הממשק של ה Hook - הפעם במקום להשתמש בפונקציה אחת אנחנו קוראים ל-2, הפונקציה onMounted ו onUnmounted, ופה גם המפתח להבין את ההבדלים בין הספריות. בעוד שריאקט הכניסה את המילה Effect בתור אוסף של 3 חלקים, ב Vue הלכו על גישה יותר Low Level בה אנחנו פשוט מגדירים איזה קוד להפעיל מתי. הייתרון בגישה הוא הפשטות ועקומת הלימוד היותר קלה, כי לא צריך לחשוב מה זה אפקט ומתי טוב או לא טוב להשתמש בו, אבל החיסרון הוא שיש יותר מקום לטעויות כשצריך לחשוב למשל באיזה מצבים רוצים ליצור מחדש את ה Event Handler. הקשר בין onMounted ל onUpdated יכול להיות מסורבל לכתיבה ולכן בריאקט עברו ממודל של Lifecycle למודל של אפקטים, אבל מצד שני המודל החדש בריאקט יצר עקומת לימוד הרבה יותר משמעותית ובפרספקטיבה של השנים שעברו מאז יצירת המודל אני לא בטוח שזה היה רעיון טוב.

מה דעתכם? איזה מהגישות אהבתם יותר? ואיזה מאפיינים ייחודיים של כל ספריה אתם אוהבים?