• בלוג
  • צעדים ראשונים עם סוליד

צעדים ראשונים עם סוליד

02/03/2022

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

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

1. מונה לחיצות

תוכנית ראשונה שנראה בסוליד היא גם התוכנית הראשונה שמופיעה בטמפלייט באתר ה playground שלהם - וזה מונה לחיצות:

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);
  const increment = () => setCount(count() + 1);

  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  );
}

render(() => <Counter />, document.getElementById("app"));

אתם יכולים לפתוח אותו ב Playground בקישור https://bit.ly/3tlqP5r.

עכשיו לקוד:

  1. הפונקציה createSignal מחליפה את useState. היא מחזירה מערך של "ערך" ו"פונקציית עדכון". עד פה אין הפתעות.

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

const increment = () => setCount(count() + 1);

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

import { template, render, createComponent, delegateEvents, insert } from 'solid-js/web';
import { createSignal } from 'solid-js';

const _tmpl$ = template(`<button type="button"></button>`, 2);

function Counter() {
  const [count, setCount] = createSignal(0);

  const increment = () => setCount(count() + 1);

  return (() => {
    const _el$ = _tmpl$.cloneNode(true);

    _el$.$$click = increment;

    insert(_el$, count);

    return _el$;
  })();
}

render(() => createComponent(Counter, {}), document.getElementById("app"));

delegateEvents(["click"]);

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

2. חמש תיבות טקסט מסונכרנות

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

import { render } from "solid-js/web";
import { createSignal, For } from "solid-js";
const range = (n: number) => (new Array(n)).fill(null).map((_, i) => i);

const [text, setText] = createSignal("");

function Text() {
  function handleInput(e: InputEvent) {
    const input = e.target as HTMLInputElement;
    setText(input.value)
  }

  return (
    <input type="text" value={text()} onInput={handleInput} />
  );
}

function App() {
  const n = 5;

  return (
    <div>
    <p>n={n}</p>
    <For each={range(n)}>
      {(item, index) => <Text />}
    </For>
    </div>
  )
}

render(() => <App />, document.getElementById("app"));

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

חדי העין ביניכם יכולים לראות גם ששם האירוע של השינוי השתנה מ change ל input כדי ליישר קו עם קוד DOM סטנדרטי:

<input type="text" value={text()} onInput={handleInput} />

ואולי השינוי הכי גדול מריאקט זה הלולאה:

<For each={range(n)}>
  {(item, index) => <Text />}
</For>

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

insert(_el$2, createComponent(For, {
  get each() {
    return range(n);
  },

  children: (item, index) => createComponent(Text, {})
}), null);

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

אפשר לשחק עם הדוגמה השניה ב Playground שלהם בקישור https://bit.ly/36IOdlE

3. משיכת מידע מ API

בדוגמה האחרונה אני רוצה למשוך מידע מ API באמצעות fetch. אני אגדיר קומפוננטה של פוקימון שתיקח פרמטר id ותמשוך את השם של הפוקימון משרת pokeapi.co. כל פעם שה id משתנה נרצה למשוך מחדש את המידע ולהציג את השם המעודכן.

זה הקוד:

import { render } from "solid-js/web";
import { createSignal, For, splitProps, createEffect } from "solid-js";

function Pokemon(props) {
  const [local, others] = splitProps(props, ["id"]);
  const [name, setName] = createSignal("");

  createEffect(async () => {
    let isActive = true;
    const url = `https://pokeapi.co/api/v2/pokemon/${local.id}`;
    console.log(`Fetching url ${url}`)
    setName('');
    const res = await fetch(url);    
    const data = await res.json();

    if (isActive) {
      setName(data.name);
    }

    onCleanup(() => { isActive = false });
  });

  return (
    <p>Pokemon {local.id} is named {name}</p>
  )
}

function App() {
  const [id, setId] = createSignal(1);

  return (
    <>
    <input type="number" value={id()} onInput={(e) => setId(e.target.value)} /> 
    <Pokemon id={id()} />
    </>
  );
}

render(() => <App />, document.getElementById("app"));

הרבה דברים מעניינים כאן:

  1. הפונקציה createEffect היא המקבילה בסוליד ל useEffect, אבל היא לא צריכה לקבל מערך של דברים שמשפיעים על האפקט. באופן אוטומטי היא מסתכלת על קריאות ל getters בתוך הפונקציה ומושפעת רק משינויים במשתנים אלה.

  2. במקום שפונקציית אפקט תצטרך להחזיר פונקציית ביטול לאפקט, יש לנו פונקציה בשם onCleanup שמקבלת את פונקציית הניקוי.

  3. פיענוח ה Properties דורש טיפול מיוחד:

const [local, others] = splitProps(props, ["id"]);

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

יכולים לשחק עם הקומפוננטה האחרונה ב Playground שלהם בקישור https://bit.ly/3toBdJC.

סך הכל יש משהו יפה בגישה הריאקטיבית של סוליד ורוב הקוד שכתבתי בו פשוט עבד. אהבתי את הגמישות שאפשר ליצור משתני State גם מחוץ לקומפוננטות ובסך הכל אתר התיעוד שלהם וה Playground עובדים ממש בסדר. למידע נוסף ואינסוף משאבים עליו ממליץ לחטט ברשימה הזאת: https://github.com/one-aalam/awesome-solid-js.