• בלוג
  • שלום GraphQL חלק 2: חיבור אפליקציית ריאקט עם Relay

שלום GraphQL חלק 2: חיבור אפליקציית ריאקט עם Relay

14/09/2020

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

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

בפוסט זה אציג דוגמת קוד עובדת של React ו Relay שקוראת מידע מה Github API וכך תוכלו לראות גם איך החלקים של GraphQL ו React מתחברים יחד וגם לקבל נקודת התחלה לפרויקט שלכם שישתמש בכלים אלה.

1. קצת על Relay

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

  1. ניהול מטמון (cache) גלובאלי לכל הבקשות כדי שקומפוננטות שונות על המסך יוכלו לשתף מידע שהגיע מהרשת.

  2. ניהול הזדהות מול השרת במקום אחד.

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

  4. וידוא כל שאילתה מול סכימה של ה API בזמן בניית הקוד כך שטעויות יזוהו כבר בזמן הקידוד ולפני זמן הריצה.

  5. קומפילציה של שאילתות לקוד JavaScript מהיר כדי לחסוך עבודה בזמן ריצה.

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

2. איך נראית קומפוננטת ריאקט בתוך ריליי

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

הקוד הבא מייצג כזו קומפוננטה:


export default function App(props) {
  const [login, setLogin] = useState('ynonp');
  const loginInput = useRef(null);

  function getRepoCount(e) {
    e.preventDefault();
    setLogin(loginInput.current.value);
  }

  return (
    <QueryRenderer
      environment={environment}
      query={graphql`
          query AppQuery($login: String!) {
            user(login: $login) { 
              repositories {
                  totalCount
              }
            }
          }
        `}
          variables={{login}}
          render={({error, props}) => {
            if (error) {
              return <div>Error!</div>;
            }
            return (
              <div>
                <form onSubmit={getRepoCount}>
                  <input type="text" defaultValue={login} ref={loginInput} />
                  <button>Get Repo Count</button>
                </form>
                <UserData {...props} login={login} />
              </div>
            );
          }}
    />
  );
}

החלק המעניין של הקוד מתחיל באלמנט QueryRenderer. אלמנט זה הוא חלק מ Relay ותפקידו לחבר בין קומפוננטה לבין שאילתת GraphQL. דרך המאפיין render שלו הוא מקבל פונקציה ומעביר לה אוביקט עם המפתחות error ו props. המפתח error מכיל את השגיאה (אם היתה) ויותר מעניין המפתח props שמכיל את תוצאת השאילתה.

בקוד הדוגמה אני פונה לשרת של גיטהאב ומבקש לקבל כמה ריפוזיטוריז יש למשתמש מסוים. התוצאה היא אוביקט JSON עם המפתח user, בתוכו המפתח repositories ובתוכו המפתח totalCount, והערך שלו הוא המספר שאני צריך. תוצאה זו עוברת לפונקציה שהעברתי ל render בתור שדה props.

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

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

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

3. הקמת סביבת ריליי

סביבת Relay בנויה מרכיב שנקרא Store שאחראי על שמירת המידע, חיבורו לקומפוננטות ועדכון הקומפוננטות כל פעם שיש מידע חדש; ומרכיב בשם network שאחראי על כל התקשורת והמידע שעובר ברשת.

בשביל לעבוד ב Relay עלינו ליצור את שני הרכיבים ולחבר אותם לרכיב עוטף שנקרא Environment. לכל אחד מהרכיבים יש מימוש ברירת מחדל ב Relay, אבל לגבי רכיב הרשת אני לא הצלחתי לעבוד עם רכיב ברירת המחדל שלו ומצאתי מימוש יותר מתוחכם בספריה בשם react-relay-network-modern.

הקוד המלא להקמת הסביבה נראה כך:

import { Environment, RecordSource, Store } from 'relay-runtime';
import {
  RelayNetworkLayer,
  urlMiddleware,
  authMiddleware,
} from 'react-relay-network-modern';

const REACT_APP_GITHUB_AUTH_TOKEN = "WRITE-YOUR-GITHUB-TOKEN-HERE";


const network = new RelayNetworkLayer(
  [
    urlMiddleware({
      url: (req) => Promise.resolve('https://api.github.com/graphql'),
    }),
    authMiddleware({
      token: REACT_APP_GITHUB_AUTH_TOKEN,
    }),
  ],
);

const source = new RecordSource();
const store = new Store(source);
const environment = new Environment({ network, store });

export default environment;

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

יש עוד המון אפשרויות ל react-relay-network-modern שלא כללתי בקוד הדוגמה, כולל אפשרות להכנסת Cache, להריץ קוד על כל מידע שנכנס למערכת, לרכז בקשות ולהוציא אותן במנות ועוד. חפשו בתיעוד לפני שאתם מתחילים לעבוד איתו בצורה רצינית.

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

4. קוד הפרויקט המלא

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

את הסכימה בפרויקט הדוגמה לקחתי מגיטהאב עצמם (כל מי שמפרסם GraphQL API צריך לתת איתו היום גם סכימה שמגדירה מה ה API יודע לעשות). מיקום הסכימה נשמע בקובץ הגדרות הקומפיילר של הפרויקט שהוא relay.config.js.

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

$ yarn relay --watch

ובשני:

$ yarn start

החלון העליון אחראי על קומפילציית השאילתות של GraphQL והשוואתן לסכימה. ניסוי ששווה לעשות אם הגעתם עד לכאן הוא לשנות את השאילתה למשהו לא הגיוני (למשל להוסיף שגיאת כתיב או לנסות לשלוף מידע שלא קיים ב API) ולראות איך חלון הקומפילציה מציג שגיאה.