היכרות עם RTK Query

20/10/2022

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

1. מי צריך את RTK Query

ספריית RTK Query מאפשרת לכתוב את כל קוד התקשורת של היישום בנפרד מקוד הקומפוננטות בתוך משהו שנקרא Service. כל סרביס אחראי על תקשורת עם API מסוים ומכיל את כל המנגנונים לחיבור לאותו API ואת כל הלוגיקה בחיבור הזה.

"לוגיקה בחיבור הזה" אתם שואלים? מה זאת אומרת "לוגיקה בחיבור ל API", הרי כל מה שצריך בשביל להתחבר ל API זה לשלוח בקשת HTTP, למשל עם שורה כמו:

const response = await (await fetch(url)).json();

זאת היתה שורה אחת. ואם אנחנו כבר ביישום ריאקט אז יש ספריות כמו swr ו react-query שנותנות לנו אפילו קיצור דרך לשורה הזאת:

const { data, error } = useSWR(url);

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

  1. קונפיגורציה של ה endpoint לפי סביבת עבודה.

  2. זיהוי משתמש, וחיבור מחדש בצורה אוטומטית אם הטוקן פג תוקף.

  3. מנגנון דפדוף כשיש הרבה תוצאות.

  4. שינוי מבנה התשובה אם השרת לא מחזיר את המידע בדיוק בצורה שאני צריך אותו.

  5. שילוב בין HTTP ל Web Sockets - כלומר שליפת המידע בפעם הראשונה דרך HTTP, ואז פתיחת Web Socket כדי לקבל עדכונים על אותו שדה מידע.

כשאני כותב את קוד התקשורת במקום אחד יש לי רק מקום אחד לכתוב בו את כל הלוגיקה סביב התקשורת. ו RTK Query מספקת בדיוק את המקום הזה.

העבודה עם RTK Query תתן לנו את אותן יכולות של swr או react-query, כלומר הספריה תאפשר לנו לתשאל את ה API מכמה קומפוננטות ובאופן אוטומטי תשמור את התשובות כדי לא לשלוח יותר מדי בקשות לשרת וכדי לשפר ביצועים, ובנוסף היא תאפשר לכתוב את כל הלוגיקה שלנו שקשורה לבקשות במקום אחד.

בגלל שספריית RTK Query תומכת גם ב GraphQL וגם ב Rest, יהיה לנו קל להחליף את שכבת התקשורת או הפרוטוקול בלי לשנות את הקומפוננטות, מה שמשאיר לנו גמישות יותר גדולה בפיתוח היישום.

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

2. בניית ממשק לקריאת פתקים

בעבודה עם RTK Query רכיב התקשורת הבסיסי נקרא Service. הוא הולך לקבל סלייס ב Store והוא ייצור בצורה אוטומטית Custom Hooks אותם נוכל להפעיל מתוך הקומפוננטות.

בשביל המשחק בניתי API לעבודה עם פתקים על mockapi ואנחנו נכתוב תוכנית ראשונה שמציגה פתקים מתוך אותו API. ה Service המפורסם יראה כך:

// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

// Define a service using a base URL and expected endpoints
export const notesApi = createApi({
  reducerPath: "notesApi",
  baseQuery: fetchBaseQuery({
    baseUrl: "https://634fa864df22c2af7b5647a4.mockapi.io/api/v1/"
  }),
  endpoints: (builder) => ({
    getNotes: builder.query({
      query: () => `/notes`
    }),
    getNote: builder.query({
      query: (noteId) => `/notes/${noteId}`
    })
  })
});

export default notesApi;

בואו נראה מה הוא כולל:

  1. הפונקציה הראשית createApi מקבלת אוביקט פרמטרים ויוצרת סרביס.

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

  3. המפתח baseQuery מקבל אוביקט שאחראי על יצירת השאילתות. ברירת המחדל היא האוביקט fetchBaseQuery שהוא מעטפת פשוטה סביב Fetch API, אבל קל לכתוב אוביקטים כאלה חדשים (למשל עבור GraphQL), ויש גם המון מוכנים ב npm.

  4. המפתח endpoints מתאר את נקודות הקצה שיש ב API. הוא מקבל פונקציה שמקבלת אוביקט builder ובאמצעותו בונה את אוביקט נקודות הקצה שהיא מחזירה. מה שחשוב לראות כאן זה את ההפעלה של builder.query לכל נקודת קצה לקריאה - כלומר כזו שקוראת את רשימת הפתקים או כזו שקוראת פתק מסוים. פונקציות אחרות על builder יאפשרו פעולות נוספות כמו mutation שיוצרת נקודת קצה לעדכון.

באופן אוטומטי כל Endpoint שאני מגדיר הופך ל Custom Hook אותו אוכל להפעיל מקומפוננטת ריאקט. לכן בקוד ריאקט עוד מעט אוכל לכתוב:

const { data, error, isLoading } = useGetNotesQuery();

ואני לא צריך להעביר כאן את ה URL או שום דבר, כי הכל כבר כתוב בתוך הסרביס.

בקוד סנדבוקס הזה תוכלו למצוא את התוכנית המלאה שמשתמשת בסרביס שכתבתי ומציגה על המסך רשימה של פתקים: https://codesandbox.io/s/rtk-demo-fgs64q

מה שמעניין כאן הוא שהתוכנית מגדירה שתי קומפוננטות וכל קומפוננטה מפעילה את useGetNotesQuery. אם תסתכלו ב Network Tab בכלי הפיתוח תוכלו לראות שיש רק פניה אחת ל API כדי לקבל את רשימת הפתקים, ו RTK Query משתמש באותה תוצאה לכל הקומפוננטות.

3. דפדוף בתוצאות

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

קוד צד השרת כבר תומך בדפדוף באמצעות הפרמטרים page ו limit, ולכן מספיק לי לעדכן בקוד ה service את הפונקציה getNotes כדי שתוסיף פרמטרים אלה עם ערכי ברירת מחדל:

endpoints: (builder) => ({
  getNotes: builder.query({
    query: ({ page = 1, limit = 20 }) => `/notes?page=${page}&limit=${limit}`
  }),

וזה לדעתי הכח של RTK Query - היכולת לשנות במקום אחד ולקבל עדכון אוטומטי של כל הקומפוננטות ביישום שניגשות לנתיב מסוים. שימו לב שהפונקציה ב query חייבת לקבל רק פרמטר אחד, כי היא משתמשת בפרמטר הזה בתור Cache Key, לכן אני מעביר שם אוביקט עם שני השדות page ו limit.

בקוד עצמו אני יכול להחליט להעביר ערכים לפרמטרים אלה, או להישאר עם ערכי ברירות המחדל. אני הוספתי תיבה כדי לבחור את העמוד בקומפוננטה שמציגה את הפתקים, ובקומפוננטה שמציגה את מספר הפתקים העברתי ערך -1 למשתנה limit כדי לבטל את הגבלת התוצאות. הקוד לגירסה המעודכנת בקישור: https://codesandbox.io/s/rtk-demo-part-2-yxleqj

4. לאן ממשיכים

ל RTK Query יש עוד המון יכולות מעבר למה שהראיתי כאן בפוסט, ואני ממליץ לחפש בתיעוד לפי הנושאים הבאים כדי ללמוד עליה יותר:

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

  2. שימוש בספריות fetch אחרות - לדוגמה משיכת מידע משרת GraphQL

  3. מחיקת פריט מה Cache ושליפתו מחדש

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

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