You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/he/7-bank-project/4-state-management/README.md

19 KiB

בניית אפליקציית בנקאות חלק 4: מושגים בניהול מצב

חידון לפני ההרצאה

חידון לפני ההרצאה

מבוא

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

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

דרישות מקדימות

עליך להשלים את החלק של שליפת נתונים באפליקציית הווב עבור שיעור זה. כמו כן, עליך להתקין את Node.js ולהריץ את שרת ה-API באופן מקומי כדי שתוכל לנהל נתוני חשבון.

תוכל לבדוק שהשרת פועל כראוי על ידי ביצוע הפקודה הזו בטרמינל:

curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result

חשיבה מחדש על ניהול מצב

בשיעור הקודם (כאן), הצגנו מושג בסיסי של מצב באפליקציה שלנו עם המשתנה הגלובלי account שמכיל את נתוני הבנק של המשתמש המחובר כרגע. עם זאת, ליישום הנוכחי שלנו יש כמה פגמים. נסה לרענן את הדף כשאתה נמצא בלוח הבקרה. מה קורה?

ישנן 3 בעיות בקוד הנוכחי:

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

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

אילו בעיות אנחנו באמת מנסים לפתור כאן?

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

  • איך לשמור על זרימות הנתונים באפליקציה מובנות?
  • איך לשמור על נתוני המצב תמיד מסונכרנים עם ממשק המשתמש (ולהיפך)?

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

סכימה שמראה את זרימות הנתונים בין ה-HTML, פעולות המשתמש והמצב

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

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

משימה

נתחיל עם קצת רפקטורינג. החלף את ההצהרה account:

let account = null;

ב:

let state = {
  account: null
};

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

עלינו גם לעדכן את הפונקציות שמשתמשות בו. בפונקציות register() ו-login(), החלף account = ... ב-state.account = ...;

בראש הפונקציה updateDashboard(), הוסף את השורה הזו:

const account = state.account;

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

מעקב אחר שינויים בנתונים

עכשיו, כשיש לנו את אובייקט ה-state לאחסון הנתונים שלנו, השלב הבא הוא לרכז את העדכונים. המטרה היא להקל על המעקב אחר כל שינוי ומתי הוא מתרחש.

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

ב-JavaScript, תוכל להשתמש ב-Object.freeze() כדי ליצור גרסה בלתי ניתנת לשינוי של אובייקט. אם תנסה לבצע שינויים באובייקט בלתי ניתן לשינוי, תתעורר חריגה.

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

משימה

בואו ניצור פונקציה חדשה בשם updateState():

function updateState(property, newData) {
  state = Object.freeze({
    ...state,
    [property]: newData
  });
}

בפונקציה הזו, אנחנו יוצרים אובייקט מצב חדש ומעתיקים נתונים מהמצב הקודם באמצעות אופרטור הפיזור (...). לאחר מכן, אנו מחליפים מאפיין מסוים של אובייקט המצב עם הנתונים החדשים באמצעות הערת סוגריים [property] לצורך השמה. לבסוף, אנו נועלים את האובייקט כדי למנוע שינויים באמצעות Object.freeze(). כרגע יש לנו רק את המאפיין account במצב, אבל עם הגישה הזו תוכל להוסיף כמה מאפיינים שתרצה.

נעדכן גם את האתחול של state כדי לוודא שהמצב ההתחלתי נעול גם הוא:

let state = Object.freeze({
  account: null
});

לאחר מכן, עדכן את פונקציית register על ידי החלפת ההשמה state.account = result; ב:

updateState('account', result);

עשה את אותו הדבר עם פונקציית login, והחלף state.account = data; ב:

updateState('account', data);

כעת ננצל את ההזדמנות לתקן את הבעיה של אי ניקוי נתוני החשבון כאשר המשתמש לוחץ על התנתקות.

צור פונקציה חדשה בשם logout():

function logout() {
  updateState('account', null);
  navigate('/login');
}

ב-updateDashboard(), החלף את ההפניה return navigate('/login'); ב-return logout();

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

טיפ: תוכל לעקוב אחרי כל השינויים במצב על ידי הוספת console.log(state) בתחתית updateState() ופתיחת הקונסולה בכלי הפיתוח של הדפדפן שלך.

שמירת המצב

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

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

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

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

אפשרות נוספת היא להשתמש באחת מה-APIs הרבות של הדפדפן לאחסון נתונים. שתיים מהן מעניינות במיוחד:

  • localStorage: מאגר מפתחות/ערכים שמאפשר לשמור נתונים ספציפיים לאתר הנוכחי בין סשנים שונים. הנתונים שנשמרים בו אינם פגים לעולם.
  • sessionStorage: זה עובד כמו localStorage פרט לכך שהנתונים שנשמרים בו נמחקים כאשר הסשן מסתיים (כאשר הדפדפן נסגר).

שימו לב ששני ה-APIs הללו מאפשרים לשמור רק מחרוזות. אם ברצונך לשמור אובייקטים מורכבים, תצטרך לסדר אותם לפורמט JSON באמצעות JSON.stringify().

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

משימה

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

const storageKey = 'savedAccount';

לאחר מכן, הוסף את השורה הזו בסוף פונקציית updateState():

localStorage.setItem(storageKey, JSON.stringify(state.account));

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

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

function init() {
  const savedAccount = localStorage.getItem(storageKey);
  if (savedAccount) {
    updateState('account', JSON.parse(savedAccount));
  }

  // Our previous initialization code
  window.onpopstate = () => updateRoute();
  updateRoute();
}

init();

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

אנחנו גם יכולים להפוך את דף לוח הבקרה לדף ברירת המחדל של האפליקציה שלנו, מכיוון שעכשיו אנחנו שומרים את נתוני החשבון. אם לא נמצאו נתונים, לוח הבקרה דואג להפניה לדף התחברות בכל מקרה. ב-updateRoute(), החלף את ברירת המחדל return navigate('/login'); ב-return navigate('/dashboard');.

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

רענון הנתונים

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

עבור ללוח הבקרה באמצעות חשבון test, ואז הרץ את הפקודה הזו בטרמינל כדי ליצור עסקה חדשה:

curl --request POST \
     --header "Content-Type: application/json" \
     --data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
     http://localhost:5000/api/accounts/test/transactions

נסה לרענן את דף לוח הבקרה בדפדפן עכשיו. מה קורה? האם אתה רואה את העסקה החדשה?

המצב נשמר ללא הגבלת זמן בזכות ה-localStorage, אבל זה גם אומר שהוא אף פעם לא מתעדכן עד שאתה מתנתק מהאפליקציה ומתחבר שוב!

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

משימה

צור פונקציה חדשה בשם updateAccountData:

async function updateAccountData() {
  const account = state.account;
  if (!account) {
    return logout();
  }

  const data = await getAccount(account.user);
  if (data.error) {
    return logout();
  }

  updateState('account', data);
}

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

צור פונקציה נוספת בשם refresh:

async function refresh() {
  await updateAccountData();
  updateDashboard();
}

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

const routes = {
  '/login': { templateId: 'login' },
  '/dashboard': { templateId: 'dashboard', init: refresh }
};

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


🚀 אתגר

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

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

חידון לאחר ההרצאה

שאלון לאחר ההרצאה

משימה

מימוש דיאלוג "הוספת עסקה"

להלן דוגמה לתוצאה לאחר השלמת המשימה:

צילום מסך המציג דוגמה לדיאלוג "הוספת עסקה"


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