חיפוש
  • יניב אור

מערכת ניהול - React - חלק 1

עודכן ב: מרץ 21

בחלק זה אני בונה מערכת ניהול ב-React. אני עושה שימוש ב-Redux וב-React Hooks וכמו כן ב-styled-components לעבודה נוחה עם ה-CSS ובניית רכיבים. הסברים מפורטים בהמשך.


מערכת הניהול הזאת היא חלק מפרויקט גדול יותר:

מערכת מבוססת microservices


כשהחלק שעוסק במערכת הניהול הוא:

מערכת מבוססת microservices - חלק 2 - מערכת ניהול - React


אני לא משתמש ברכיבי UI מוכנים או ספריות שמיועדות לכך - בפרויקט הזה. אני רוצה לכתוב את רוב הקוד from-scratch.


האייקונים ממטריאל: https://material.io/resources/icons/?style=baseline


ה-repo נמצא כאן: https://github.com/YanivOr/admin-system-react


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


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

$ ngx create-react-app admin-system-react


הרכיב הראשי וניתוב

יצרתי מתחת ל-src תיקיה חדשה בשם: app וכך נראה הרכיב הראשי - App - שיכיל את שאר הרכיבים:

src/app/App.js


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


את ניהול הניתובים אני עושה בעזרת רכיב נפרד בשם: CustomRoute. את התמיכה ב-Redux כתבתי אחרי שסיימתי עם הרכיב של הניתוב, לכן לא השתמשתי ב-store בחלק הזה. אולי אוסיף בהמשך. כרגע, יש רק שימוש ב-hooks והעברת props.


ל-CustomRoute אני מעביר את המשתנה שמחזיק את ה-state - שנקרא במקרה שלנו routeState. הוא מאותחל בערך INIT ב-useState. כמו כן, אני מעביר את הפונקציה setRoute שקוראת ל-setState - הפונקציה שמעדכנת את ה-state בהתאם ל-route שנבחר.


src/app/CustomRoute.js

יש שלושה מצבים:

1. מצב INIT: המצב ההתחלתי או במקרה שה-JWT token לא תקין. במצב הזה הרכיב שירונדר יהיה ה-Loading.

2. מצב REDIRECT: במקרה שה-JWT token תקין ו-redirect הוא true. הרכיב שירונדר יהיה רכיב Redirect שיעשה העברה ל-root (/). אני עושה redirect כשה-token מופיע ב-URL ואני רוצה "לנקות" את שורת הכתובת בדפדפן.

3. מצב ROUTE: במקרה שה-JWT token תקין ואין העברה. הרכיב שירונדר יהיה רכיב Switch שיכיל Routes שונים לפי הדפים במערכת.


המצבים הללו תלויים בתוצאה (redirect ו-isTokenValid) של הפונקציה checkAuth שנקראת בצורה אסינכרונית.


src/services/auth.js

בפונקציה checkAuth אני אוסף את ה-token מה-localStorage ובמקביל גם מה-URL. במידה והגיע ערך מה-localStorage או במידה והיה token ב-URL ורק אם ה-path ללא ה-querystring הוא / (עוד ניסיון להקשות על התחכמויות) - אני מעביר את ה-token לפונקציה validateToken, לאימות.


הפונקציה validateToken כרגע בודקת תקינות של אורך, שבאופן שרירותי קבעתי שיהיה מעל 256 תוים. יש קריאה לפונקציה authenticate שכרגע מחזירה תמיד resolve(true). בהמשך אשלח את ה-token לאימות מול שרת ה-auth.


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


ניהול ה-store עם Redux

ה-store מוגדר ב-App.js (מופיע ומוזכר למעלה) וקורא לפונקציה configureStore דרך הניתוב של src/store/configureStore.js לפי prod או dev. ב-dev יש כמה תוספות לפיתוח, כמו למשל שימוש ב-devtools או ב-hot-reload ל-reducers עם webpack.


פתחתי תיקיות actions ו-reducers מתחת ל-src. לכל ישות, דף וכו' יש תיקיה משלה. ניקח לדוגמא את הישות accounts. בקובץ הבא נמצאים הקבועים של accounts.


src/actions/accounts/constants.js


וקובץ זה הוא קובץ הפעולות.

src/actions/accounts/index.js

אני משתמש ב-axios כדי להעביר בקשה לשרת API שמטפל ב-accounts. אני אוסף את ה-JWT token מה-localStorage ומעביר אותו ב-Authorization header.


מתבצעת קריאה לאקשן GET_ACCOUNTS_STARTED מיד בתחילת ריצת הפונקציה getAccounts. לאחר זמן מה - במידה וחזר מידע - מתבצעת קריאה לאקשן GET_ACCOUNTS_SUCCESS כשה-payload הוא המידע שהתקבל מהשרת. אחרת ובכל מצב של שגיאה - מתבצעת קריאה לאקשן GET_ACCOUNTS_FAILURE כשה-payload הוא error.


נעבור ל-reducer:


src/reducers/accounts/index.js

כרגע פשוט מאוד ויש פה עוד עבודה. מעבר לזה, יש בתיקיה לצד הקובץ הזה קובץ schema שלא קשור לניהול ה-store וכו'. כרגע שמתי אותו שם. בהמשך אעביר את ה-schema בצורה אחרת.


הרכיבים במערכת

הרכיבים במערכת נמצאים בתיקיות שונות מתחת לsrc/components. התיקיות הן:

- תיקיית Common - מכילה רכיבים שעתידים להיות בשימוש יותר מפעם אחת במערכת, במקומות שונים. רכיבים כמו טבלה, loading, כותרות וכדומה.

- תיקיית General - מכילה רכיבים כלליים שלרוב יהיו בשימוש פעם אחת. תפריט, למשל.

- תיקיית Layout - מכילה רכיב שיוצר שלד בעל header, בעל footer ותוכן ביניהם. זה הרכיב שעוטף את רוב הדפים במערכת.

- תיקיית Views - מכילה את הדפים במערכת, בד"כ לפי הגדרות ה-routes.


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

נעבור לרכיבים עצמם ונראה את המימוש שלהם והיחסים ביניהם.


רכיב ה-Layout

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


נעבור לקובץ הראשי:

src/components/Layout/index.js

הרכיב Layout מכיל את Wrapper שהוא div שמוגדר כ-flex, כשהכיוון של האלמנטים תחתיו הוא מלמעלה למטה. הוא מוגדר ברוחב ואורך של 100%.


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


דבר נוסף, הרכיב הזה מקבל כ-props ילדים (children) - שהם יכולים להיות כל רכיב אחר, במקרה שלנו Dahboard, Accounts, Posts. ה-children עוברים שוב כ-children לרכיב Content, שאחראי להציג אותם בסופו של דבר.


גם בחלק הזה של המערכת עדיין לא השתמשתי ב-Redux וכרגע נעשה שימוש בהעברת פונקציות מהורה לילד כ-props וכמו כן שימוש ב-hooks. התחלתי איזשהו initialState שכתוב בקובץ הזה ולא ברור אם ישאר שם כשיגדל. בקיצור, נכתב פה משהו מהיר על מנת להמשיך הלאה ובהמשך אעביר את ניהול האירועים של ה-header וה-menu ל-store וכולי. בכל מקרה זה יהיה יעיל אם אראה שימושים ב-hooks ו-props ובהמשך אראה איך לעשות הסבה של קוד כזה לניהול ב-store אז נמשיך.


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


ה-state הזה עובר כ-prop שנקרא isVisible לרכיב Menu.


src/components/General/Menu.js

המאפיין isVisible מועבר ל-Wrapper שהוא ה-div הראשי שמחזיק את התפריט - וקובע את הגדרת ה-CSS של המיקום האופקי (left). הוספתי גם transition ל-left כדי ליצור אנימציה, כך שאם isVisible הוא true - התפריט יופיע על המסך כשהוא מחליק משמאל לימין. להיפך - כש-isVisible הוא false.


פרט למאפיין isVisible, הועבר לתפריט מאפיין נוסף: btnClicked שהוא מיפוי לפונקציה menuItemClicked שנמצאת ברכיב Layout (הקובץ src/components/Layout/index.js). ברגע שלוחצים על אחד הפריטים בתפריט, הפונקציה הזאת מתבצעת ובנוסף הדפדפן מבצע העברה ל-route הנכון - בעזרת הרכיב Link. הפונקציה menuItemClicked משנה את ה-state של isVisible ל-false ומעלימה את התפריט.


נעובר לרכיב ה-Header.


src/components/Layout/Header.js

גם לרכיב הזה הועברה פונקציה כ-prop - הפונקציה btnClicked שהיא מיפוי לפונקציה menuBtnClicked ב-Layout. ברגע שלוחצים על כפתור התפריט - היא מתבצעת. מה שהיא עושה זה להפוך את ה-state של isVisible למצב הנגדי. אם התפריט מופיע, להעלים אותו - ולהיפך.


רכיבי ה-View

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


src/components/Views/Accounts.js

זה הקובץ הראשי שמחזיק את הניהול של ה-accounts. כרגע, יש רק טבלה אבל בהמשך יתוספו עוד אלמנטים, כגון טפסים וכדומה. כאן מתבצע ניהול ה-state וקריאה לפעולות ב-store בכל מה שקשור ל-accounts. בינתיים, getAccounts שנקראת כשהרכיב נטען (ב-useEffect).


הרכיב הזה עושה שימוש ברכיב כללי יותר שנקרא Entity. הרכיב Entity מקבל כ-props תוכן לכותרת, schema - שזה בעצם יהיה אובייקט שמתאר את השדות שיופיעו בטבלה ובטופס (כרגע, מערך פשוט של שדות) - ומקבל גם data, שבמקרה שלנו accounts.


src/components/Views/Entity.js

הרכיב Entity מציג את הכותרת בהתאם ל-title שהתקבל וכמו כן מעביר את שאר המאפיינים לרכיב טבלה.


src/components/Common/Table/index.js

טבלת HTML רגילה. מורכבת מספר רכיבי styled-components ובנוסף מהרכיבים: Head ו-Row. שניהם מקבלים את schema וכך מרנדרים את השדות המתאימים והערכים המתאימים. כמו כן, אני מריץ map על ה-data ומייצר Row לכל שורה.


src/components/Common/Table/Head.js

אני רץ על ה-schema ומייצר את התאים של כותרות השדות בטבלה.


src/components/Common/Table/Row.js

אני רץ על ה-schema וממלא את התאים בערכים, לפי ה-item שקיבלתי.


אציג רכיב נוסף שכתבתי. זהו הרכיב שמציג את דף הטעינה:


src/components/Common/Loading.js

במקום להשתמש ב-Animated GIF עם אנימציה מוכנה אני משתמש ב-PNG - רצוי להשתמש ב-SVG אם יש תמונה מתאימה - ויוצר אנימציה של סיבוב בעזרת CSS בלבד. אחד היתרונות של PNG ושל SVG על פני GIF הוא בשקיפות הרקע, שלא קיימת באחרון. יתרון נוסף הוא היכולת לשלוט בכיוון הסיבוב, במהירות וכולי. היתרון של SVG על פני PNG הוא בשמירה על מראה תקין גם בשינויי רזולוציה.


המשך בחלק הבא:

מערכת ניהול - React - חלק 2


© 2023 by DO IT YOURSELF. Proudly created with Wix.com