ru
Feedback
ToCode

ToCode

Открыть в Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Больше
1 419
Подписчики
Нет данных24 часа
Нет данных7 дней
Нет данных30 день
Архив постов
ToCode
1 419
# מדריך קוד: שליפה מבסיס נתונים ב Node.JS והצגת המידע כטבלא ב HTML בפוסט זה אדגים איך לכתוב אפליקציית Node.JS ו Express פשוטה, ששולפת מידע מבסיס נתונים באמצעות ספריית Knex.JS ומציגה אותו בתור טבלה ב HTML. חסרי הסבלנות ביניכם מוזמנים לדלג על ההסבר ולקפוץ ישר לקוד במאגר: https://github.com/ynonp/node-knex-ejs-demo לכל השאר - בואו נראה איך זה עובד. ## מה אנחנו בונים המשימה במדריך הזה מאוד פשוטה - יש לנו אפליקציית Node.JS שמשתמשת ב Express וב EJS. אני יודע כולם רגילים לראות ריאקט ו Ajax ופריימוורקס מתוחכמים של פרונט אנד, אבל האמת שיש הרבה אפליקציות בעולם שעדיין כתובות בגישה הקלאסית של יצירת HTML בצד שרת, כולל אפליקציות חדשות. בכל מקרה המערכת בדוגמה שלנו היא כזאת - יש לה EJS View שנראה בערך כך:
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
  </body>
</html>
ועכשיו אנחנו רוצים לחבר אותה לבסיס נתונים. בשביל הדוגמה יצרתי בסיס נתונים מסוג SQLite3 עם טבלה אחת בשם users ובתוכה הנתונים הבאים:
sqlite> select * from users;
user|user@mysite.com
admin|admin@mysite.com
info|info@mysite.com
sales|sales@mysite.com
לעמודה הראשונה קוראים name ויש בה את שם המשתמש ולעמודה השניה קוראים email והיא מכילה את כתובת המייל של המשתמש. ## הקובץ lib/db.js אני אוהב להתחיל בקובץ חיבור לבסיס נתונים. אם השליפות מתוחכמות או המערכת גדולה יהיה לי נוח לכתוב בקובץ הזה גם פונקציות עזר לשליפות, אבל בדוגמה שלנו הכל עדיין פשוט ולכן מספיק לכתוב בו את קוד החיבור לבסיס הנתונים ולייצא ממנו את האוביקט knex, איתו קוד חיצוני יוכל לכתוב את השליפות:
const knex = require('knex')({
  client: 'sqlite3', // or 'better-sqlite3'
  connection: {
    filename: "./mydb.sqlite"
  },
  useNullAsDefault: true,
});

module.exports = knex;
## הקובץ routes/index.js התחנה השניה היא קוד הטיפול בבקשה בקובץ routes/index.js. קוד זה צריך לשלוף את המידע מבסיס הנתונים. כל עוד אנחנו יודעים שאין הרבה מידע אפשר להשתמש ב:
router.get('/', async function(req, res, next) {
  const users = await knex('users').select('*');
  res.render('index', { title: 'Express', users: users });
});
ככל שיהיה יותר מדי נצטרך לשלוף רק "דף" אחד ממנו באמצעות הוספת limit. נרצה גם לתת למשתמשים אפשרות להעביר פרמטרים כדי לקבוע כמה רשומות יהיו בדף ובאיזה דף לצפות, ובמערכות עוד יותר מורכבות נרצה לקבל גם פרמטרים עבור סינונים - לדוגמה פרמטר שמגיע מהמשתמש שאומר שצריך להציג רק את המשתמשים שכרגע מחוברים למערכת. שינויים אלה משפיעים על שורת השליפה, ונשים לב לשני הדברים החשובים בפונקציה הקטנה הזו: 1. אני משתמש ב await כדי לקבל את התוצאות ממש של השליפה, ולא את השליפה עצמה. 2. אני מעביר את התוצאות ל View. ## הצגת המידע כטבלה בקובץ views/index.ejs הקובץ האחרון הוא ה View. קובץ זה רץ בלולאה על הרשומות במשתנה users ובונה מהן טבלת HTML:
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <p>All users = <%= JSON.stringify(users) %></p>
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Email</th>
        </tr>
      </thead>
      <tbody>
        <% for (user of users) { %>
        <tr>
          <td><%= user.name %></td>
          <td><%= user.email %></td>
        </tr>
        <% } %>
      </tbody>
    </table>
  </body>
</html>
אני הוספתי גם הדפסה של כל הנתונים בפורמט JSON בשביל שנוכל להיות בטוחים שהטבלה מציגה את כל המידע. כמובן שבמערכת אמיתית זה משהו שנרצה להוריד. ## מה הלאה ראינו בפוסט את המסלול שעובר מידע מבסיס הנתונים, דרך קוד צד שרת ועד שהופך ל HTML. ההרחבה המרכזית לדוגמה הזאת תהיה קבלת פרמטרים מהמשתמש כדי להציג רק חלק מהמידע כאשר יש בטבלה בבסיס הנתונים יותר מדי מידע. ספריה מעניינת בהקשר הזה היא knex-paginate, שתתן לכם לכתוב קוד כזה:
const result = await knex('persons')

ToCode
1 419
# היום למדתי: הסימן נקודותיים סלאש בגיט לכבוד פסח, קבלו טיפ גיט קצר שאני גיליתי לאחרונה ואולי יעזור גם לכם - והוא הסימן נקודותיים ואחריו לוכסן. משמעות הסימן היא התיקיה הראשית בפרויקט, והוא מאפשר לנו להריץ פקודת גיט על התיקיה הראשית גם אם כרגע אנחנו בתוך איזושהי תת תיקיה. דוגמאות? בטח- נניח שאתם רוצים לראות את כל הקבצים שגיט עוקב אחריהם במאגר, אבל אתם כרגע בתוך תיקיה פנימית. אתם כותבים:
$ git ls-files
ולא מקבלים כלום או מקבלים רק את הקבצים שנמצאים אצלכם בתיקיה הפנימית. במקום זה, תוכלו לכתוב:
$ git ls-files :/
ותקבלו את רשימת כל הקבצים שבמעקב. או, נניח שאתם באותה תיקיה פנימית ובהפעלת סטטוס אתם מקבלים:
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   ../agenda.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        ./
        ../react-list/

no changes added to commit (use "git add" and/or "git commit -a")
מה שאומר שגם התיקיה שאני נמצא בה אינה שמורה בגיט, גם הקובץ agenda.md שנמצא בתיקיה מעליי השתנה וגם התיקיה react-list שנמצאת בתיקיה מעליי אינה שמורה בגיט. במצב כזה אני יודע שאני יכול להפעיל add על התיקיה שבדיוק מעליי כדי להוסיף את שלושת הקבצים, אבל ככל שהשינויים יהיו מפוזרים ביותר תיקיות זה אולי יהיה מסובך. במקום זה אני מפעיל:
$ git add :/
ומוסיף לאינדקס את כל הקבצים שהשתנו בכל הפרויקט.

ToCode
1 419
ייתרון ראשון שאנחנו רואים בהתקנת git-lfs הוא שפעולות add ו commit הופכות להרבה הרבה יותר מהירות. הסיבה היא שגיט מנסה לכווץ קבצים שהוא מכניס למאגר, וכיווץ קובץ בינארי ענק הוא פשוט בזבוז זמן. עם git lfs אנחנו חוסכים את הבזבוז הזה. ייתרון שני ואולי יותר מלהיב הוא האפשרות לעשות clone למאגר בלי הקבצים הגדולים. הפקודה:
$ GIT_LFS_SKIP_SMUDGE=1 git clone https://github.com/ynonp/large-files-with-lfs.git skip-smudg
e
משכפלת את המאגר תוך דילוג על משיכת הקבצים הגדולים והשארת רק המידע המנהלתי עליהם. אחרי הפעלה אני יכול לראות שתיקיית העבודה שלי נשארה קטנטונת (וכמובן פעולת ה clone היתה מאוד מהירה):
$ cd skip-smudge
$ du -sh .
140K    .
אם ננסה להסתכל על תוכן הקובץ נקבל את אותה גירסה שהיתה שמורה במאגר:
$ cat myfile.bin
version https://git-lfs.github.com/spec/v1
oid sha256:fc4d5fa4f28329049cb1c1bdda562a7e6aa3472efbf94cc280f1f2aa9c6790a5
size 104857600
ומתי שאצטרך אני יכול להוריד את הקבצים הגדולים בגירסה שכרגע נמצאת אצלי עם הפקודה:
$ git lfs pull
בדוגמה שלנו הפקודה מורידה רק עותק אחד של הקובץ myfile.bin ולא את שני העותקים שיש במאגר.

ToCode
1 419
# מדריך: איחסון קבצים גדולים בגיט עם git lfs כולם אומרים שגיט לא טוב באיחסון קבצים בינאריים. בפוסט זה אני רוצה להסביר קצת מה הבעיה של גיט עם קבצים בינאריים גדולים, ולהראות את התוסף git lfs שפותר חלק גדול מאותה בעיה. ## החיים עם קבצים גדולים הדרך הכי קלה (וברובה נכונה) לחשוב על ניהול המידע של גיט היא שכל קומיט בגיט הוא עותק של כל תיקיית העבודה ברגע מסוים. בתוך ההקשר הזה ברורה הבעיה עם קבצים גדולים - הם נשמרים שוב ושוב ומנפחים את המאגר. ניצור בשביל הדוגמה מאגר עם קובץ גדול. אני מפעיל את הפקודה:
$ dd if=/dev/random of=myfile.bin bs=1m count=100
ומקבל קובץ בשם myfile.bin בגודל 100 מגה. אחרי זה אני יוצר מאגר גיט ומוסיף את הקובץ למאגר ו-הופ, הפרויקט שלי תופס כבר 200 מגה - כי יש לי עותק אחד של הקובץ הגדול בתיקיית העבודה ועותק נוסף במאגר בתיקיית .git. אם אני יוצר גירסה נוספת של הקובץ ומוסיף גם אותה למאגר התיקיה תעבור כבר את ה 300 מגה. זה אומר שכשאני דוחף את המאגר לגיטהאב אני צריך להעלות 300 מגה. כשמישהו אחר מוריד עותק של המאגר הוא צריך להוריד 300 מגה. וככל שיהיו יותר גירסאות לקובץ הגדול, כך הגודל של המאגר יגדל וכל הפעולות יהיו איטיות בצורה מורגשת. ## אז מה זה git lfs התוסף git lfs עושה דבר חמוד - במקום לשמור את הקבצים הגדולים במאגר, הוא מזהה אותם בדיוק לפני שהם נכנסים למאגר ומעביר אותם לתיקיה אחרת, שאינה חלק מהמאגר. בתוך הריפו יישמרו רק מצביעים לאותם קבצים, ומי שירצה את הקבצים הגדולים יצטרך להוריד אותם בנפרד. התיקיה האחרת בדרך כלל תשב בתוך תיקיית .git ובשביל להזיז את הקבצים הגדולים לתיקיה המיוחדת git lfs משתמש ב filter, שזה מנגנון של גיט שמאפשר להריץ פקודה לפני שקובץ נכנס לריפו ופקודה אחרת כשהקובץ צריך לצאת מהריפו לתיקיית העבודה. בשביל לעבוד עם git lfs אתם צריכים להתקין את התוסף לכל המערכת שלכם מהאתר שלהם: https://git-lfs.github.com/ ואחרי זה בכל ריפו שרוצים לעבוד איתו צריך להפעיל בתוך תיקיית הריפו:
$ git lfs install
## איך זה עובד דוגמה? ברור. יצרתי ריפו שיש בו קובץ גדול והפעלתי בתוכו את:
$ git lfs install
בשביל ש git lfs יוכל לעשות את הקסם שלו. הפקודה הראשונה שצריך להכיר היא git lfs track. לקובץ שלי קוראים myfile.bin ולכן אני מפעיל:
$ git lfs track -f myfile.bin
כדי לסמן ל git lfs שהקובץ myfile.bin הוא קובץ גדול וצריך לטפל בו בהתאם. אפשר גם להעביר ל track תבניות לקבצים (ואז מוותרים על ה -f) למשל:
$ git lfs track "*.bin"
הדבר הזה יוצר קובץ .gitattributes אם לא קיים אצלכם בתיקיה (או מוסיף שורה לקובץ אם כבר קיים), ושם הוא מסמן שהקובץ או התבנית נכנסים למעקב:
$ cat .gitattributes
myfile.bin filter=lfs diff=lfs merge=lfs -text
עכשיו אפשר להוסיף את הקובץ למאגר:
$ git add myfile.bin
$ git commit -m 'initial commit'
ואפילו להכניס גירסה נוספת שלו:
$ dd if=/dev/random of=myfile.bin bs=1m count=100
$ git commit -a -m 'commit 2'
ולזה היו כמה תוצאות מעניינות. הראשונה היא שתיקיית .git/objects שלי ממש קטנה:
$ du -sh .git/objects
 32K    .git/objects
וזה בגלל שהקבצים עצמם נשמרים בתיקיה אחרת:
$ du -sh .git/lfs/objects
200M    .git/lfs/objects
וכשאני מסתכל בתוך הריפוזיטורי על הקובץ:
$ git ls-files --stage -- myfile.bin
100644 e0ffbeb5780cceeeef67522f4bc092751c4e7409 0       myfile.bin

$ git cat-file blob e0ffbeb5780cceeeef67522f4bc092751c4e7409

version https://git-lfs.github.com/spec/v1
oid sha256:fc4d5fa4f28329049cb1c1bdda562a7e6aa3472efbf94cc280f1f2aa9c6790a5
size 104857600
אני רואה שאני לא מקבל את תוכן הקובץ אלא רק שלוש שורות שמספרות משהו על הקובץ המקורי. זה כל מה ש git lfs צריך בשביל להביא את הקובץ הגדול המקורי אם נבקש ממנו. כרגע הקבצים הגדולים נמצאים אצלי בתיקיית העבודה וגם כשאני דוחף את הפרויקט שלי לגיטהאב הם יישלחו לשם. הפרויקט לדוגמה נמצא כאן: https://github.com/ynonp/large-files-with-lfs ## מה הרווחנו

ToCode
1 419
# פוחדת מהפאדיחה ליפול למתכנתים צעירים רבים אין בעיה לבוא בהצעות מרחיקות לכת, להכניס כלים חדשים ופריימוורקים חדשים למערכת ואפילו ליזום ולהעביר הרצאות על נושאים שהם כמעט לא מכירים. אותם מתכנתים מבינים את הפוטנציאל במוניטין שיוכלו לצבור, ויודעים שממילא אין להם הרבה מוניטין לאבד. ככל שאנחנו גדלים המוכנות "לקחת סיכונים" נחלשת. הרווח שיצא לי מלהכניס כלי חדש למערכת הוא כנראה לא גבוה, והסיכון משמעותי. מתכנתת וותיקה עשויה להרגיש שמצפים ממנה להכיר לעומק כלי מסוים לפני שהיא ממליצה עליו לצוות; היא יודעת בדיוק מה זה אומר "להכיר לעומק" כי היא מכירה לעומק את הכלים שעכשיו עובדים איתם, ויודעת שכרגע (או בעתיד הנראה לעין) אין לה זמן ללמוד לעומק את אותו כלי חדש. הפחד מהפאדיחה ליפול היה מה שעצר אותי כשכולם עברו מ perl ל python: ידעתי שכשאני מגיע ללקוח לעזור בבעיית פרל לא משנה מה יהיה אני אוכל למצוא לו פיתרון טוב, כי כבר ראיתי אינסוף בעיות פרל ופיתרונות שלהן באותה תקופה. ב python הסיפור היה שונה לגמרי - ההיכרות שלי עם פייתון היתה מוגבלת, ולפני שרואים פרויקטים ופותרים בעיות אי אפשר באמת לקבל ביטחון. פרילאנסרים צעירים ממני נכנסו בלי להתבלבל לפרויקטים בפייתון, ואני הסתכלתי מהצד וחיכיתי, וככל שחיכיתי הקפיצה רק נראתה יותר קשה. הבעיה עם הפחד מהפאדיחה היא שבכל מקרה ספציפי ההחלטה לקחת סיכון היא באמת החלטה יותר קשה. מה טוב כבר יצא לי מלקחת פרויקט ייעוץ בפייתון כשאני לא יודע מספיק טוב פייתון? אני רק אשב המון שעות על כל בעיה קטנה, ובסוף אתן ללקוח קוד בינוני. אבל, כשמסתכלים על התמונה הגדולה, ברור שאחרי 3-4 פרויקטים אני כבר אכיר את השפה ממש טוב ואוכל להיות מועיל לעצמי ולסביבה. וכשחוזרים לעבודה היום יומית כמתכנתת בצוות - להמליץ על כלי בודד שאולי לא יעבוד טוב זה מסוכן, אבל להיות זאת שממליצה על טכנולוגיות חדשות זה כבר משהו ששווה לשאוף אליו. העניין שהדרך תמיד עוברת דרך הפאדיחה, דרך הפעם הראשונה, דרך הנפילה. במקום לפחד מהפאדיחה ליפול - כדאי להתרגל פשוט ליפול. להפוך את הנפילה לחלק מהחיים ולהבין שבטווח הרחוק, כשאנחנו בונים סגנון חיים שבו הנפילה היא לא פאדיחה אלא חלק מהיום יום שלנו, אנחנו רק מרוויחים.

ToCode
1 419
# מה כל כך מלהיב ב Tailwind CSS לפני כמה שנים לקחתי פרילאנסר לעזור בבניית HTML ו CSS לאתר שהייתי צריך ללקוח. הפרילאנסר עבד מעולה ויצר קוד שהתאים במדויק לעיצוב שהיה לנו, ואני הייתי בטוח שמצאתי את הדרך אל האושר. עד שהיינו צריכים לשנות משהו. כי הבעיה עם CSS היא שהכל תלוי שם בהכל: מאפיין של גודל גופן על אלמנט מסוים משפיע על הגופן אצל הילדים שלו, מאפיין position באלמנט מסוים משפיע על ההתנהגות של מאפייני top ו left אצל הילדים שלו. בקיצור CSS מרגיש כמו מגדל קלפים - זה עובד, אבל שינוי הכי קטן ודברים יוצאים מהמקום. נשווה את זה ל Tailwind CSS, שהוא, לטובת מי מהקוראים שלא מכירים, פריימוורק שמעביר את ה CSS לתוך ה HTML, או אולי יותר נכון לנסח את זה בתור פריימוורק שיוצר דינמית הגדרות CSS שמתאימות לקלאסים עם שמות מאוד מסוימים ב HTML. אז ב Tailwind CSS אני יכול לכתוב שורת HTML כזאת:
<img
    src="http://placekitten.com/100/100" 
    alt="cat"
    class="py-4 pl-4 aspect-square"
/>
ואני אקבל תמונה של חתלתול עם ריפוד עליון וריפוד משמאל של 16 פיקסלים כל אחד ומאפיין aspect-ratio עם ערך של 1/1. ומה כל כך מלהיב בלהעביר את כל כללי העיצוב ל HTML? אם נחזור לסיפור של הפרילאנסר, לתחזק קוד Tailwind CSS של מישהו אחר (בהנחה שאנחנו מכירים טיילווינד) זה הרבה יותר קל מלתחזק קוד CSS של מישהו אחר. אני לא צריך להיכנס ולהבין איזה קלאסים הוא בחר ומה המבנה ב HTML שמתאים לקוד ה CSS הקיים; הכל פשוט מופיע מול העיניים. רוצים לדעת עוד על טיילווינד והקלאסים שלו? הפוסט הזה הוא מקום טוב להתחיל בו: https://codingthesmartway.com/tailwind-css-for-absolute-beginners/ וכמובן התיעוד המעולה שלהם בקישור: https://tailwindcss.com/docs/installation

ToCode
1 419
# קוד מלוכלך דוד בוב אוהב לדבר על קוד נקי - קוד שאין בו חזרות, שמסודר נכון, שקל להרחיב אותו, שקל לקרוא אותו, שעושה בדיוק את מה שצריך ולא עושה דברים שלא צריך. כל אחד היה רוצה לכתוב רק קוד נקי כל הזמן. אבל אם ניקח את המטאפורה של קוד נקי ברצינות, נשים לב ששום דבר לא יוצא נקי מהמפעל, ושעם הזמן גם דברים נקיים מתלכלכים. ככה גם קוד: הניסיון הראשון בפיתרון בעיה לא ייצא נקי. אחרי הקידוד צריך לנקות, כלומר לעשות Refactoring, להוסיף תיעוד ולכתוב בדיקות. גם אחרי שעשינו את הכל כמו בספר, תוך כמה שבועות או חודשים הקוד יתלכלך שוב, בדיוק כמו כל דבר אחר בעולם. קוד נקי הוא לא תוצאה של מתכנתים גאונים שיודעים איך לכתוב קוד, והוא לא תוצאה של שינון הכללים וניסיון ללכת לפי הספר. קוד נקי הוא תוצאה של גישה ושיטת עבודה. שיטה שמשאירה זמן ונותנת עדיפות גם לניקיון ולא רק לפיתוח הראשוני. קוד נקי הוא לא מצב קיים אלא תהליך, תהליך שמתחיל בקוד מלוכלך.

ToCode
1 419
בעוד עשר דקות מתחילים וובינר על Solid.JS, מוזמנים להיכנס בקישור:

ToCode
1 419
הי בוקר טוב לכולם מזכיר שהיום בעשר נקיים וובינר על Solid Js תכף אשלח פה את הלינק לכניסה

ToCode
1 419
בתיקיית src/app של הפרויקט תוכלו למצוא קובץ בשם store.js שמחזיק את התוכן הבא:
import { configureStore } from '@reduxjs/toolkit';
import memoryReducer from '../features/memory/memorySlice';
import memory from '../features/memory/memoryMiddleware';

export const store = configureStore({
  reducer: {
    memory: memoryReducer,
  },
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(memory),
});
תכף נדבר על המידלוור, אבל בינתיים בואו נתמקד ב Reducer - הפונקציה configureStore, גם היא מ Redux Toolkit, מחזירה Redux Store לפי רשימת ה Reducers שתעבירו לה, ואת ה Reducer אני מייבא מהקובץ הקודם שכתבנו. ## קומפוננטת ריאקט החלק הכי טוב בעבודה עם רידאקס הוא שכל הלוגיקה כבר קיימת בלי קשר לממשק, ולכן כשאני מגיע לכתוב קומפוננטת ריאקט אני בסך הכל צריך להבין איזה "מידע" משפיע על הקומפוננטה ואיזה פעולות היא יכולה לעשות. במקרה של משחק הזיכרון המידע הוא רשימת הקלפים והפעולה היחידה היא ללחוץ על קלף. הנה הקוד של הקומפוננטה:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  play,
  selectCards,
} from './memorySlice';
import './memory.css';

export function MemoryGame() {
  const cards = useSelector(selectCards);
  const dispatch = useDispatch();

  return (
    <div>
      {cards.map((c, i) => (
        <div
          className={c.hidden 
          ? "card hidden" : c.found
          ? "card found" : "card"
          }
          onClick={() => dispatch(play(i))}
          key={i}
        >
          {c.value}
        </div>
      ))}
    </div>
  );
}
## המידלוור עד לפה היה יכול להיות לנו קוד שמראה רשימת קלפים וחושף קלפים כשאנחנו לוחצים עליהם, וגם צובע בצהוב קלפים זהים שמצאנו. בשביל משחק אמיתי אנחנו צריכים גם להסתיר בחזרה את הקלפים וכאן Redux Middleware הוא פיתרון שמתאים כמו כפפה ליד: 1. כל פעם שאני שולח פקודת play המידלוור יכול להריץ את הפקודה ולבדוק אם אחרי שהסתיימה יש שני קלפים גלויים שעדיין לא מצאנו את הזוג שלהם. אם כן, הוא ישים טיימר של שתי שניות ובעוד שתי שניות ישלח בקשה להפוך אותם. 2. כל פעם שאני מקבל פקודת play לפני הרצת הפקודה המידלוור יבדוק אם יש איזה טיימר פעיל, ואם כן הוא יבטל אותו ופשוט יריץ מיד את האיפוס. המחשבה כאן היא שאם מישהו לוחץ על קלף "שלישי" צריך מיד להסתיר את שני הגלויים מהתור הקודם. הקוד שמתאים לתיאור המילולי שלי הוא בסך הכל:
const gameMiddleware = ({ dispatch, getState }) => next => action => {
  // before play, if we're waiting for a "check" we'll
  // cancel the timer, check immediately and then continue
  // to play
  if (action.type === play.type && activeTimer) {
    clearTimeout(activeTimer);
    activeTimer = null;
    dispatch(check());
  }

  let result = next(action)

  // after play we count 2 seconds and check the status
  if (action.type === play.type) {
    if (selectVisibleCards(getState()).length === 2) {
      activeTimer = setTimeout(() => {
        dispatch(check());
      }, 2000);
    }
  }

  return result
};
וזה בעצם כל משחק הזיכרון שלנו. הנה מה שהרווחנו בעבודה עם Redux Toolkit: 1. כל הלוגיקה מרוכזת בפונקציות פשוטות, שמקבלות אוביקט סטייט ואוביקט action ומשנות את הסטייט לפי הבקשה ב action. 2. אם בעתיד נרצה להוסיף מנגנונים, מאוד ברור לנו איך לעשות את זה בלי "לקלקל" או לשנות את הקוד הקיים. פשוט צריך להוסיף עוד סלייס. הקוד היחיד שאצטרך לשנות הוא הקובץ store.js, כדי להוסיף שם התיחסות לסלייס החדש. 3. קומפוננטת ריאקט שיצרתי יצאה מאוד פשוטה. הלוגיקה היחידה שבה היא לוגיקה שקשורה לתצוגה. 4. למרות שלא הראיתי כאן, יהיה קל מאוד לכתוב קוד בדיקה ללוגיקה כי כולה מורכבת מפונקציות טהורות. 5. למרות שלא הראיתי כאן, רידאקס טולקיט מגיע עם כל כלי ה debug של רידאקס אז יש לכם את כל הפינוקים של מסע אחורה בזמן וצפיה בכל ה actions שעוברות במערכת. סך הכל כתיבת רידאקס היום היא הרבה יותר פשוטה ממה שהיתה בעבר והרבה בזכות redux toolkit. את קוד המשחק המלא אתם יכולים למצוא במאגר כאן: https://github.com/ynonp/redux-memory-game

ToCode
1 419
# מדריך קוד: בואו נכתוב משחק זיכרון ב Redux ו React לפני שנתיים כתבתי כאן משחק זיכרון עם ריאקט בעזרת הפונקציה useReducer. היום אני רוצה לנסות משחק דומה עם Redux כדי לראות מה התחדש ברידאקס בשנים האחרונות ובמיוחד כדי לשחק עם Redux Toolkit, ספריה שהופכת את הכתיבה ברידאקס למשחק הרבה יותר מהנה. ## הסוד של רידאקס טולקיט הוא בכלל Immer האמת חייבת להיאמר, כתיבת קוד JavaScript שמתעסק עם Immutable Data זה לא תענוג. אם אני צריך לקחת אוביקט סטייט שמורכב מאוביקטים פנימיים ולשנות משהו באחד האוביקטים הפנימיים שלו אני מהר מאוד מוצא את עצמי כותב דברים כאלה:
function handwrittenReducer(state, action) {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue,
        },
      },
    },
  }
}
ומפה הדרך לאבדון קצרה. הטריק הכי מלהיב עם Redux Toolkit זה החיבור המובנה שלה עם Immer. אימר, למי שלא מכיר, זו ספריית JavaScript שמאפשרת לי לכתוב קוד שמשנה מידע והופכת אותו אוטומטית לקוד שמשכפל את המידע במקום לשנות אותו. הפונקציה הכי חשובה של Redux Toolkit נקראת createSlice והיא בעצם יוצרת גם Reducer וגם Action Creators, משולבת עם Immer כך שהקוד שאני כותב בה "כאילו" משנה את האוביקטים. בואו נצלול למשחק הזיכרון כדי לראות את הסיפור הזה בפעולה. כל הקוד שאני כותב עליו זמין בריפו הזה: https://github.com/ynonp/redux-memory-game אז במשחק זיכרון יש לי חבילה של קלפים שאני יכול ליצור עם:
const initialState = {
  cards: _.shuffle(_.range(10).map(i => ({
    value: Math.floor(i / 2),
    hidden: true,
    found: false,
  }))),
};
המאפיין value של קלף מייצג את המספר שעליו, hidden אומר אם הוא הפוך ו found אומר אם מצאנו אותו יחד עם הזוג שלו. הפונקציה הראשונה שאני צריך במשחק זיכרון היא פונקציה שנקראת כשמשתמש לוחץ על קלף. הנה המימוש המפתיע שלי:
function onPlay(state, action) {
  const index = action.payload;
  if (state.cards[index].hidden) {
    state.cards[index].hidden = false;
  }

  const visibleCards = state.cards.filter(c => !c.found && !c.hidden);
  if (visibleCards.length === 2) {
    if (visibleCards[0].value === visibleCards[1].value) {
      visibleCards[0].found = true;
      visibleCards[1].found = true;
    }
  }
}
ולמה זה מפתיע? כי אנחנו יודעים שב Redux כל Reducer צריך לקבל state ו action ולהחזיר state חדש. פה אני פשוט משנה את ה state במה שנראה כמו שינוי במקום:
state.cards[index].hidden = false;
באופן אוטומטי ספריית immer הופכת את הקוד הזה לקוד שלא באמת משנה את הסטייט אלא יוצר סטייט חדש ומחזיר אותו. אגב פונקציה שניה שתעזור לי במשחק זיכרון תופעל כמה שניות אחרי התור השני בשביל להפוך את כל הקלפים שצריך להסתיר:
function onCheck(state) {
  for (let c of state.cards) {
    if (!c.found) {
      c.hidden = true;
    }
  }
}
את כל הקוד הזה כתבתי בקובץ שנקרא memorySlice.js ועד עכשיו לא היה שום דבר שקשור לרידאקס או ל redux-toolkit בקובץ. החלק האחרון הוא כבר נקודת הכניסה ל Redux:
export const memorySlice = createSlice({
  name: 'memory',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: { play: onPlay, check: onCheck },
});
הפונקציה createSlice מקבלת את שתי הפונקציות שכתבתי לתוך מפתח reducer של אובייקט הפרמטרים שלה ויוצרת גם Reducer מסודר וגם Action Creators. עכשיו אני יכול להשתמש בערך ההחזר שלה כדי לייצא את התוצאות:
export const { play, check } = memorySlice.actions;
export default memorySlice.reducer;
לסיום הקובץ אני יוצר ומייצא עוד שתי פונקציות עזר איתן קוד מקבצים אחרים יוכל לקרוא מידע מה State:
export const selectVisibleCards = (state) => state.memory.cards.filter(c => !c.hidden && !c.found);

export const selectCards = (state) => state.memory.cards;
## איך משתמשים ב Reducers

ToCode
1 419
# שאלה של גישה לא מזמן דיברתי עם מתכנת ריאקט מאוד מוכשר, ששיכנע אותי בנאומים חוצבי להבות שהדרך הנכונה לכתוב קוד ריאקט היא להשתמש ב useEffect וב Custom Hooks כדי להעביר כמה שיותר לוגיקה, כולל לוגיקה של תקשורת, לתוך הקומפוננטות. הרבה יותר קל, הוא טען, לנהל את הלוגיקה של השליפות כשהשליפה קרובה לקוד שמציג את התוצאה שלה. בנוסף, כשאתה מוחק את הקומפוננטה אוטומטית אתה מוחק את קוד השליפה שקשור אליה, ולא נשאר עם מנגנוני שליפה שאף אחד לא משתמש בהם. ואז מצאתי את המשפט הבא בתיעוד של Redux Toolkit: > With RTK Query, you usually define your entire API definition in one place. This is most likely different from what you see with other libraries such as swr or react-query, and there are several reasons for that. Our perspective is that it's much easier to keep track of how requests, cache invalidation, and general app configuration behave when they're all in one central location in comparison to having X number of custom hooks in different files throughout your application. בתרגום וקיצור הם אומרים שהם בנו את RTK Query כי הרבה יותר קל לנהל תקשורת כשכל הקוד שמתעסק בה נמצא במקום אחד. "מי צודק" זאת בכלל לא השאלה כאן. הרבה אתגרים בתכנות בנויים ככה שאפשר לפתור אותם בכל מיני דרכים. כל גישה מגיעה עם הכלים שלה ובכל גישה אפשר לבנות פיתרונות טובים (או פחות טובים). הדרך להיות מתכנתים טובים יותר היא לא למצוא את "הגישה האחת" שתמיד נכונה, אלא להיפתח ליותר גישות, ולהבין בתוך גישה מסוימת מה הסטנדרטים שמקובלים בה, מה זה אומר לכתוב קוד טוב (או גרוע) כשהלוגיקה בתוך הקומפוננטות, ומה זה אומר לכתוב קוד טוב (או גרוע) כשהלוגיקה נמצאת במקום אחר, ואיזה מלכודות או סכנות צפויות לנו בכל אחת מהגישות.