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

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

דוגמת ריפקטורינג ב React עם Custom React Hook

13/11/2019

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

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

import React from 'react';
import ReactDOM from 'react-dom';
import { useState, useEffect } from 'react';
import _ from 'lodash';
import $ from 'jquery';

const App = () => {
  const [id, setId] = useState(1);
  const [character, setCharacter] = useState({});
  const [filmId, setFilmId] = useState(1);
  const [film, setFilm] = useState({});

  useEffect(function() {
    setFilm({});
    const req = $.getJSON(`https://swapi.co/api/films/${filmId}/`, function(data) {
      setFilm(data);
    });
    return function cancel() {
      req.abort();
    }

  }, [filmId]);

  useEffect(function() {
    setCharacter({});
    const req = $.getJSON(`https://swapi.co/api/people/${id}/`, function(data) {
      setCharacter(data);
    });
    return function cancel() {
      req.abort();
    }
  }, [id]);

  const characterNameText = (character.name == null ?
    '[Loading please wait]' :
    character.name);

  const filmNameText = (film.title == null ?
    '[Loading please wait]' :
    film.title);

  const filmIds = (character.films != null ?
    character.films.map(f => f.match(/(\d+)\/$/)[1]) :
    _.range(1, 8));

  return (
    <div>
      <h1>ID: {id}</h1>
      Character ID:
      <select onChange={(e) => setId(e.target.value)}>
        {_.range(1, 11).map(id => (
          <option value={id}>{id}</option>
        ))}
      </select>
      Film ID :
      <select onChange={(e) => setFilmId(e.target.value)}>
        {filmIds.map(id => (
          <option value={id}>{id}</option>
        ))}
      </select>
      <hr />
      <h2>Character Name: {characterNameText}</h2>
      <h2>Film Name: {filmNameText}</h2>
    </div>
  )
};


const root = document.querySelector('main');
ReactDOM.render(<App />, root);

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

איך מתקנים? כל מה שצריך הוא להוציא את הקוד הכפול לפונקציה:

export function useRemoteData(endpoint) {
  const [id, setId] = useState(1);
  const [data, setData] = useState({});

  useEffect(function() {
    setData({});
    const req = $.getJSON(`https://swapi.co/api/${endpoint}/${id}/`, function(data) {
      setData(data);
    });
    return function cancel() {
      req.abort();
    }

  }, [id]);

  return [id, setId, data];
}

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

const App = () => {
  const [characterId, setCharacterId, character] = useRemoteData('people');
  const [filmId, setFilmId, film] = useRemoteData('films');
  // ...

וכן לריאקט ממש לא אכפת שאתם קוראים מפונקציית הפקד לפונקציה נוספת והיא זו שקוראת בפועל ל useEffect ו useState. למעשה האפשרות הזו להוציא קוד החוצה היא הסיבה המרכזית למעבר ל React Hooks. הפונקציה useRemoteData נקראת Custom Hook וזה כל מה שצריך לדעת בשביל לשתף קוד מבוסס סטייט בין קומפוננטות שונות או לבצע שימוש חוזר בקוד כזה בתוך אותה קומפוננטה.

מימוש יישום מרובה שפות באמצעות React Context

21/03/2019

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

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

  1. מדובר על מידע גלובאלי שכל רכיב ביישום משתמש בו.

  2. מדובר על מידע שביסודו הוא Immutable, או לפחות שלא משתנה לעתים קרובות.

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

דוגמאות נפוצות ל Context כוללות: הגדרות שפה, הגדרות עיצוב גלובאליות ו Redux Store.

המשך קריאה

הפונקציה useEffect פותרת את אחת הבעיות הישנות ביותר שאני זוכר עם ריאקט

20/03/2019

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

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

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

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

לסקרנים שביניכם הכנתי קודפן לדוגמא. זה נראה ככה (אבל תכל'ס עדיף לא להסתכל):

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

function Character(props) {
  const [data, setData] = useState({});

  useEffect(function() {
    const req = $.get(`https://swapi.co/api/people/${props.id}`, function(data) {
      setData(data);
    });
    return function() {
      req.abort();
    }
  }, [props.id]);

  return <p>id = {props.id}, name = {data.name}</p>
}

וכאן יש קודפן כדי להוכיח שזה עובד (גם אני לא האמנתי):

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

איך (ולמה) לכתוב React Hook

17/03/2019

ריאקט עברה ועוברת עדיין הרבה טלטלות מבחינת ה API, כאשר אולי הגדולה ביותר היתה ההמלצה להפסיק להשתמש ב React.createClass ולעבור להשתמש במחלקות וב Function Components כדי ליצור את הקומפוננטות.

נודה על האמת הפונקציה React.createClass היתה סוג של פשרה מהיום הראשון. הרי אם היו class-ים ב JavaScript כשריאקט התחילה כנראה לא היו צריכים לכתוב אותה. לפונקציה זו מקבילה בהרבה ספריות UI אחרות ישנות יותר כמו למשל Class.create של פרוטוטייפ או declare של dojo.

אבל משהו מאוד גדול הלך לאיבוד במעבר מ React.createClass ל class המודרני, ולמשהו הזה קוראים Mixins. מיקסינס הפכו במעבר למחלקות למבנה שנקרא Higer Order Components שהיה הרבה פחות אינטואיטיבי לרוב המפתחים. בפוסט היום ניזכר יחד מה היו מיקסינס, איך HoC אמורים היו להחליף אותם ומה Hooks מצליחים לעשות טוב יותר. מוכנים?

המשך קריאה

פיתוח React עם TypeScript

20/01/2019

אחרי שפייסבוק ירדו מ Flow ועברו ל TypeScript חשבתי שכדאי לנסות גם - אז לקחתי את משחק תפוס ת'אדום וכתבתי אותו עם React ו TypeScript. הנה המסקנות וגם מדריך קצר איך להתחיל פרויקט React ו TypeScript.

המשך קריאה

איך להתחיל פרויקט React ו Webpack מאפס על המכונה שלכם

12/01/2019

עם השידרוג ל Webpack 4 גם כל ההגדרות של הכלי הפכו הרבה יותר פשוטות והיום בהרבה פרויקטים כבר לא צריך להתחיל עם משהו גדול כמו create-react-app ואפשר יחסית בקלות לבנות לעצמכם קובץ הגדרות Webpack שיגדל יחד עם הפרויקט שלכם.

בואו נראה איך לגשת לזה מאפס ולהגיע לפרויקט ריאקט שעובד.

המשך קריאה

השוואת גבהים בריאקט

17/12/2018

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

המשך קריאה

הסאגה של React ו ref

15/11/2018

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

בואו נדבר קצת על ref ועל השינויים במנגנון שקרו בשנים האחרונות.

המשך קריאה

פיתוח טפסים חכמים עם ריילס, ריאקט ו Context API

11/11/2018

ספריית Rails מגיעה עם מנגנון לבניה אוטומטית של טפסים חכמים. שני המנגנונים המרכזיים שטפסים אלה מייצרים הם הגנה מפני CSRF ויצירת שמות לשדות באופן אוטומטי. בניה של מנגנון דומה ב React תעזור לנו להבין את ה Context API של ריאקט וגם לקבל קוד נקי יותר.

המשך קריאה