טיפ ריסלקט - שימו לב כשאתם מפעילים filter או map בתוך Selector
ספריית Reselect עוזרת לנו לבנות פונקציות שמושכות מידע מ Redux Store. ריסלקט מגיעה כחלק מספריית Redux Toolkit ובשביל להשתמש בה בפרויקט אנחנו צריכים רק לייבא את הפונקציה המרכזית שלה createSelector
:
import { createSelector } from "@reduxjs/toolkit";
בשביל להבין למה צריך את createSelector נצטרך להיזכר איך עובדת הפונקציה useSelector
בה אנחנו משתמשים בתוך קומפוננטות:
בתוך קוד קומפוננטה אני קורא ל
useSelector
כדי "לחבר" בין קוד הקומפוננטה לנתיב מסוים באוביקט ה State ב Redux.הפרמטר שאני מעביר ל
useSelector
הוא פונקציה בעצמו - פונקציה שמקבלת את כל הסטייט ומחזירה נתיב מסוים, לדוגמה אני יכול למצוא בתוך קוד קומפוננטה את השורה:
const todos = useSelector((state: AppState) => state.todos);
כדי לגשת לכל מערך ה todos שיש בסטייט.
כל פעם שמישהו באיזשהו מקום במערכת עושה dispatch, כל פונקציות ה
useSelector
יתעוררו ויפעילו את הפונקציות שהן קיבלו כפרמטרים. אלה שיחזירו ערך חדש יגרמו לרינדור מחדש של הקומפוננטה. זה אומר שבכל Action שישלח ל Store, הקוד שלי יסתכל על השדהtodos
וישווה אותו לערך הקודם.לפעמים אני רוצה להפעיל חישובים מורכבים בתוך
useSelector
. במצב כזה אולי לא משתלם להפעיל את כל החישוב מחדש, במיוחד אם אני יודע במה החישוב תלוי. לדוגמה נתבונן ב Selector הבא:
const todosCount = useSelector((state: AppState) => state.todos.length);
אנחנו יודעים בוודאות שרק אם state.todos השתנה יש סיכוי בכלל שהאורך ישתנה (גם לא בטוח, אבל יש סיכוי). כל עוד state.todos מחזיר את אותו ערך, אין טעם לחשב מחדש את ה length שלו כי הוא בטוח יצא אותו דבר. וזאת בדיוק המטרה של Reselect.
- הספריה Reselect מאפשרת לנו לתאר את הקשר בין Selector-ים שונים, וכך לחסוך חישובים מיותרים. סלקטור יחושב מחדש רק אם חלק מהתלויות שלו השתנו.
בואו נראה עוד דוגמה קצת יותר מורכבת אבל עדיין בעולם של Todos:
import { createSelector } from "@reduxjs/toolkit";
import { AppState } from "./store";
const todos = (state: AppState) => state.todos;
export const todosThatStartWithA = createSelector(todos, (todos) =>
todos.filter((t) => t.message.startsWith("a"))
);
export const finishedTodosThatStartWithA = createSelector(
todosThatStartWithA,
(todos) => todos.filter((t) => t.completed)
);
export const numberOfFinishedTodosThatStartWithA = createSelector(
finishedTodosThatStartWithA,
(finishedTodosThatStartWithA) => {
console.log(`Recalculating the length`);
return finishedTodosThatStartWithA.length;
}
);
יש לי כל מיני שאלות שקשורות ל todos, למשל:
מי ה todos במערך שההודעה שלהם מתחילה ב a ?
מי ה todos במערך שההודעה שלהם מתחילה ב a, והם מסומנים בתור completed ?
כמה todos יש במערך שגם ההודעה שלהם מתחילה ב a וגם מסומנים בתור completed ?
השימוש ב createSelector מספק דרך נוחה לשלב כמה Selectors, וגם לחשב מחדש כל Selector רק כשהתלויות שלו באמת משתנות. אבל בואו לא נתבלבל, אפילו Reselect אינו קוסם.
למרות שהכתיב נותן הרגשה כאילו ה Selectors תלויים אחד בשני, בפועל בגלל ששני ה Selectors שבאמצע משתמשים ב filter (ולכן מייצרים מערך חדש), מצב התלויות הוא שכל ה Selectors תלויים ב state.todos. ברגע שמערך ה todos משתנה מכל סיבה שהיא, כל ה Selectors יחושבו מחדש, כי todosThatStartWithA
ו finishedTodosThatStartWithA
תמיד מחזירים מערך חדש. אם נריץ את הקוד נגלה שכל שינוי ב todos, גם אם ה todos שהשתנו לא מתחילים ב a, עדיין גרם לחישוב מחדש של ה Selector האחרון.
בקיצור גם כשרידאקס נותן לכם מתנות, תמיד צריך לשים לב לתלויות שלנו ולרינדורים כפולים. זה לא תמיד בעיה אבל כן כדאי להיות מודעים למה קורה.