ToCode
Відкрити в Telegram
1 419
Підписники
Немає даних24 години
Немає даних7 днів
Немає даних30 день
Архів дописів
1 419
# קוד שעה ביום
יש את הקוד שאתם כותבים במסגרת העבודה, הקוד שצריך לפתור בעיה, לעשות משהו מועיל. אני יודע, גם אני כותב כזה. זה אחלה, ולא עליו אני מדבר.
כי חוץ ממנו יש את הקוד שאנחנו כותבים כדי ללמוד:
1. את הקוד שראיתי בוידאו ואני מקליד כדי לראות איך זה עובד.
2. את הקוד שמצאתי בספר ואני מקליד וגם משנה אותו קצת.
3. את הקוד בשביל לפתור חידת תכנות שמצאתי.
4. את הקוד שאני כותב כדי לענות על שאלה של תלמיד, או של מישהו אקראי מ Stack Overflow.
5. את הקוד שאני כותב כדי ללמוד שפת תכנות חדשה.
קוד שעה ביום מדבר על הסוג הזה, השני. על הקוד שאתם כותבים בשביל ללמוד. והטריק הוא פשוט, שעה ביום של קוד כזה היא טרנספורמטיבית. שבוע אחרי שבוע, חודש אחרי חודש של תשובות, של לימוד שפות תכנות חדשות, של קריאת ספרים ולימוד מקורסי וידאו - אלה הדברים שהופכים אתכם למתכנתים ששווה לעבוד איתם לטווח הרחוק.
(וטיפ קצר לאלה שאומרים ״אבל אין לי רעיון מה לכתוב״ - מקסים וינדרוב התחיל לא מזמן פרויקט שנקרא אתגר JS בו כל שבועיים הוא נותן לכם אתגר JavaScript חדש, והוא גם עוזר כשנתקעים. הכל עדיין במחירי הרצה אז רוצו להירשם לפני שיהיה יקר. זה הקישור)
1 419
# תוצאה ראשונה בגוגל
לפעמים התשובה הטובה ביותר תהיה בתוצאה הראשונה בגוגל. לפעמים היא תהיה ב Stack Overflow. לפעמים היא תהיה באיזה בלוג פוסט של בלוגר מפורסם ולפעמים בפוסט של בלוגר שאף פעם לא שמעתם עליו.
כבר תקופה ארוכה שאני לא מתרגש מהתוצאה הראשונה בגוגל, וכשאני מחפש תשובה לבעיה טכנית אני אפתח בטאבים את כל התוצאות של העמוד הראשון. יש לגישה זו שני יתרונות:
1. לפעמים באמת התשובה הטובה ביותר היא לא התוצאה הראשונה. וזה קורה לעתים יותר קרובות ממה שהייתם מדמיינים.
2. אפילו אם התוצאה הטובה ביותר היא הראשונה, כשאתם מסתכלים על בעיה טכנית מעניינת תמיד יש כמה גישות וכמה מקרים. קריאת 7 פוסטים ושתי תשובות מ Stack Overflow תפתח לכם את המחשבה כדי להבין טוב יותר את הדילמות והניואנסים של הבעיה.
בתור אנשי מקצוע העבודה שלנו היא להבין את הגוונים והרבדים הרבים של השאלות, הרבה יותר מאשר למצוא את התשובה הראשונה שעובדת.
1 419
ערב טוב כולם בגלל בעיה טכנית הפוסט של הבוקר לא פורסם בשעה הרגילה. מקווה שלא לקחתם ללב. הנה
הלינק:
https://www.tocode.co.il/blog/2020-08-commits-and-branches
1 419
# שש מאות ששים ותשע שורות
הפעלתי היום npm run eject בתוך יישום שנוצר עם create-react-app וקיבלתי קובץ וובפאק של 669 שורות ועוד 6 קבצי הגדרות קטנים יותר.
הסיבה שקבצי ההגדרות של create-react-app כל כך גדולים היא שהם צריכים לתמוך בקונפיגורציה הכי מתוחכמת שאפשר כדי להתאים לכמה שיותר פרויקטים. התוצאה היא שכל הפרויקטים כוללים המון פיצ'רים שרובם לא צריכים, ושהפיצ'רים שאתם באמת צריכים אולי לא נמצאים שם כי הם ספציפיים לפרויקט שלכם.
אז מה קיבלנו שם אתם שואלים? סקירה זריזה של הדברים שקפצו לי לעין:
1. קובץ manifest.json שממש יעזור לכם אם השרת שלכם צריך לבנות דינמית את ה HTML ולשתול בו בצורה דינמית את קבצי הסקריפט (אה, אתם מגישים קובץ HTML סטטי? תמשיכו ללכת אתם לא צריכים את זה).
2. הגדרות אליאס לריאקט נייטיב (אה, אתם לא כותבים ריאקט נייטיב? לא משנה, שיהיה לכם)
3. מנגנון שגורם ל webpack לא לטעון קבצי locale גדולים מספריית moment.js (חשוב, מה אתם לא משתמשים ב moment.js?).
4. מנגנון file-loader שמאפשר לנו לבצע import לקבצים רגילים מתוך קוד ה js שלנו כדי לקבל את שם הקובץ (למשל בשביל לשלב תמונות בתוך javascript).
את רוב הדברים האלה לא הייתי מכניס לקובץ webpack רגיל של הפרויקט. אולי אוסיף אותם כשאתקל בבעיה מסוימת או בצורך מסוים, אבל בטח לא בצורה גורפת. אבל הבעיה היותר גדולה עם 669 שורות של קונפיגורציה היא שאנשים רואים את הבלוק הזה ופוחדים לשנות אותו, וכך במקום להתאים את הגדרות הבניה למה שאני באמת צריך אנשים מחפשים לבנות פיתרונות ומעקפים רק בשביל להישאר בתוך ה create-react-app.
אם הפרויקט שלכם הולך להעסיק אתכם יותר מכמה ימים. אם הגדרות הבניה שלו מורכבות ואם אתם לא בטוחים איך לגשת לזה - לכו ללמוד וובפאק. אחרי זה תכתבו קובץ פשוט עם הגדרות שאתם מבינים ולאט לאט תרחיבו אותו כדי לפתור את הבעיות של הפרויקט שלכם. זה יותר מדויק, יותר משתלם וגם יהפוך אתכם למתכנתים טובים יותר.
1 419
# שש מאות ששים ותשע שורות
הפעלתי היום npm run eject בתוך יישום שנוצר עם create-react-app וקיבלתי קובץ וובפאק של 669 שורות ועוד 6 קבצי הגדרות קטנים יותר.
הסיבה שקבצי ההגדרות של create-react-app כל כך גדולים היא שהם צריכים לתמוך בקונפיגורציה הכי מתוחכמת שאפשר כדי להתאים לכמה שיותר פרויקטים. התוצאה היא שכל הפרויקטים כוללים המון פיצ'רים שרובם לא צריכים, ושהפיצ'רים שאתם באמת צריכים אולי לא נמצאים שם כי הם ספציפיים לפרויקט שלכם.
אז מה קיבלנו שם אתם שואלים? סקירה זריזה של הדברים שקפצו לי לעין:
1. קובץ manifest.json שממש יעזור לכם אם השרת שלכם צריך לבנות דינמית את ה HTML ולשתול בו בצורה דינמית את קבצי הסקריפט (אה, אתם מגישים קובץ HTML סטטי? תמשיכו ללכת אתם לא צריכים את זה).
2. הגדרות אליאס לריאקט נייטיב (אה, אתם לא כותבים ריאקט נייטיב? לא משנה, שיהיה לכם)
3. מנגנון שגורם ל webpack לא לטעון קבצי locale גדולים מספריית moment.js (חשוב, מה אתם לא משתמשים ב moment.js?).
4. מנגנון file-loader שמאפשר לנו לבצע import לקבצים רגילים מתוך קוד ה js שלנו כדי לקבל את שם הקובץ (למשל בשביל לשלב תמונות בתוך javascript).
את רוב הדברים האלה לא הייתי מכניס לקובץ webpack רגיל של הפרויקט. אולי אוסיף אותם כשאתקל בבעיה מסוימת או בצורך מסוים, אבל בטח לא בצורה גורפת. אבל הבעיה היותר גדולה עם 669 שורות של קונפיגורציה היא שאנשים רואים את הבלוק הזה ופוחדים לשנות אותו, וכך במקום להתאים את הגדרות הבניה למה שאני באמת צריך אנשים מחפשים לבנות פיתרונות ומעקפים רק בשביל להישאר בתוך ה create-react-app.
אם הפרויקט שלכם הולך להעסיק אתכם יותר מכמה ימים. אם הגדרות הבניה שלו מורכבות ואם אתם לא בטוחים איך לגשת לזה - לכו ללמוד וובפאק. אחרי זה תכתבו קובץ פשוט עם הגדרות שאתם מבינים ולאט לאט תרחיבו אותו כדי לפתור את הבעיות של הפרויקט שלכם. זה יותר מדויק, יותר משתלם וגם יהפוך אתכם למתכנתים טובים יותר.
1 419
# איך לצמצם Boilerplate ב Redux - חלק 3
בשני החלקים הקודמים למדנו איך לצמצם Boilerplate ביישום Redux באמצעות טיפול ב Action Creators וב Reducer. היום אני רוצה לדבר על הקומפוננטות ולהראות איך לכתוב הרבה פחות קוד בתוך קומפוננטה שמחוברת ל Redux ועדיין לקבל בדיוק את אותה תוצאה.
## איפה כאן ה Boilerplate?
נתבונן בקומפוננטה הבאה מתוך דוגמת ה Todo המפורסמת מהאתר של רידאקס. הקומפוננטה מסודרת בשני קבצים אבל אני מאחד אותם בשביל שיהיה קל לראות:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import React from 'react'
import PropTypes from 'prop-types'
const Link = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
</button>
)
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.visibilityFilter
})
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Link)
איפה כאן ה Boilerplate? נו, זה ה Higher Order Component כמובן: הגדרת הפונקציה mapStateToProps שתקועה בכל קומפוננטה, הגדרת הפונקציה mapDispatchToProps, וכמובן הקריאה ל connect.
## איך לצמצם את הקוד המיותר
המעבר מ Higher Order Components ל Custom Hooks איפשר לכותבי הספריה react-redux להציע API נקי יותר. אפשר לקרוא את הפרטים בתיעוד של react-redux כאן:
https://react-redux.js.org/api/hooks
בקצרה אנחנו נשתמש ב useSelector במקום ב mapStateToProps, וב useDispatch במקום ב mapDispatchToProps. הנה הקוד אחרי התיקון:
import React from "react";
import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux";
import { setVisibilityFilter } from "../actions";
const Link = props => {
const { children, filter } = props;
const active = useSelector(state => state.visibilityFilter === filter);
const dispatch = useDispatch();
const onClick = () => dispatch(setVisibilityFilter(filter));
return (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: "4px"
}}
>
{children}
</button>
);
};
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
};
export default Link;
הקוד שלנו כולל עכשיו רק קומפוננטה אחת וכל המידע שלה מחושב במקום אחד בתחילת הפונקציה. אם בעתיד נצטרך קומפוננטות מורכבות יותר יהיה לנו הרבה יותר קל לשבור את הקוד למספר קומפוננטות שכל אחת עוסקת בענייניה. הפרידה מ Higher Order Component יצרה קוד נקי יותר וגם תוצאה רזה יותר מבחינת ה Virtual DOM.1 419
# כאלה דברים פשוט יכולים לתסכל
תסכול וסקרנות הם שני רגשות מאוד קרובים. כשאנחנו באים ללמוד משהו חדש, אם אנחנו מצליחים להרגיש סקרנות במקום תסכול סיכויי ההצלחה שלנו יגדלו משמעותית. אבל, איך עושים את זה?
## למה אני מרגיש?
הרבה אנשים טועים לחשוב שרגש הוא משהו שקורה לנו כתוצאה ממשהו שקרה בעולם: ראיתי סרט עצוב אז אני מרגיש עצוב, הרחתי ריח מוכר אז אני מרגיש בבית, הצלחתי לרוץ מרתון אז אני מרגיש על גג העולם.
האמת היא שכל רגש הוא תוצאה של חיבור בין משהו שקורה בעולם לבין הסיפור שבאמצעותו אני מבין את מה שקרה. הסיפור שלי הוא הפילטר דרכו כל דבר עובר.
קל לראות את זה בדוגמא של הסרט כי ברור שלא כל מי שרואה טיטאניק יתחיל לבכות כשליאונרדו דיקפריו צולל לים, ולא כל מי שרואה במבי ייקח ללב כשאמא של במבי מתה.
כמובן שככל שהסיטואציה יותר עצובה או שמחה בעיניי יותר אנשים כך קשה להשתכנע שיש לי פה איזשהו אלמנט של בחירה. אבל באמת אם נחזור לדוגמא של הסרט אף אחד לא בוחר לבכות כשהוא רואה סרט עצוב, זה פשוט איך שחלק מהאנשים לוקחים את זה.
הרגש הוא ההתנגשות בין מנגנוני ההבנה שלי לבין משהו שקרה בעולם והוא נוצר כשאני מסתכל החוצה ומנסה להסביר לעצמי בראש את התופעה שקרתה.
## למה אני מרגיש תסכול?
רגש של תסכול מופיע כשאני מנסה לעשות משהו שאני חושב שאני יודע או צריך לדעת אבל הדבר הזה לא מצליח. אני מנסה להרכיב פאזל 12 חלקים והם לא מתחברים. איך הם לא מתחברים? זה פאזל לגיל 3... מה לא בסדר איתי?
התסכול לא מגיע רק מזה שלא הצלחתי משהו, אלא מזה שלא הצלחתי משהו שאני בטוח שהייתי צריך להצליח. סיזיפוס לא חש תסכול כשהאבן מתדרדרת שוב ושוב במורד ההר - הוא לרגע לא העלה בדעתו שהפעם זה יצליח.
אבל כשאתה מריץ את אותה פקודת git שכבר שלוש שנים עובדת כל פעם מחדש ופתאום הגיט זורק עליך הודעת שגיאה שאתה לא מבין - זה תסכול. אתה בטוח שאתה יודע לעשות את זה, אבל זה לא מצליח.
## למה אני מרגיש סקרנות?
סקרנות היא חברה די קרובה של תסכול, עם סיפור קצת שונה. את הסקרנות אני מרגיש כשאני מנסה לעשות משהו ולא מצליח, אם הגעתי לסיטואציה בלי ציפיות להצלחה.
״מעניין מה הולך לקרות עכשיו" זה המשפט שמייצג הכי טוב את הסקרנות. אה, פקודת git שניסיתי לכתוב לא עבדה למרות שעד עכשיו כל השנים היא עבדה מעולה. מעניין למה. מעניין מה יקרה אם אשנה חלק מהמתגים. מעניין מה עשיתי הפעם שונה מבדרך כלל.
כשמורידים מהסיפור את ״אני יודע לעשות את זה״, התסכול הופך לבד לסקרנות והלמידה מתחילה.
1 419
# קורסים שמפחידים אותי
אני לא פוחד ממרצים משעממים. כשההרצאה לא מעניינת אני אומנם לא ממש יכול ללמוד וקשה להתרכז, אבל היתרון הוא שמדובר במשהו מאוד בולט. מספיק לראות הקלטה אחת מהרצאה או לבוא לשיעור לדוגמא ויש אתה מבין שאתה בקורס עם מרצה משעמם ויכול להחליט אם להישאר או לברוח.
קורסים שמפחידים אותי הם קורסים שנראים טוב: קורסים עם מותג חזק, עם מרצים שיודעים לספר סיפורים ולעניין, כאלה שאתה באמת יושב ולומד מהם, אבל לומד את הדברים הלא נכונים.
וזו תופעה די ייחודית למקצוע שלנו כי בהרבה מקצועות יש את הספרות המקצועית וכולם מלמדים ממנה. אבל אצלנו בגלל שהטכנולוגיה רצה כל כך מהר, ובגלל שיש לא מעט כלים שאפילו מי שכתב אותם עדיין לא בטוח מה תהיה הדרך הטובה ביותר להשתמש בהם, הסכנה של לימוד תבניות לא נכונות היא מאוד משמעותית.
(מה זה תבניות לא נכונות? לדוגמא בשפה כמו C זה יהיה כמו ללמד את malloc בלי ללמד את free. ב JavaScript זה יהיה ללמד את הפונקציה document.write, ועוד דברים רבים בסגנון).
דוגמאות הקוד עובדות, הן נראות טוב, הן עושות מה שכביכול התוכנית צריכה לעשות - אבל אם ננסה להוציא אותן מהקורס ולשלב אותן בעולם האמיתי התוכנית תיפול או שהקוד יסתבך שלא לצורך; או בדוגמא אחרת - אם ננסה לענות על שאלה מראיון עבודה עם הדוגמא שלמדנו המראיין יזרוק אותנו מכל המדרגות.
השיטה שלי היום ללמוד נושאים חדשים היא לחפש כמה שיותר מקורות מידע, וכמה שאפשר לבדוק את הקורס שלקחתי אל מול התיעוד של הספריה או פוסטים עדכניים בתחום. כשמלמדים אותי משהו שאני לא מוצא בשום מקום אחר, זה סימן להתחיל לדאוג.
1 419
# איך לצמצם Boilerplate בקוד Redux - חלק 2
בחלק הקודם בסידרה הראיתי איך לצמצם Boilerplate בקוד של ה Action Creator ב Redux. היום פוסט המשך בו נטפל ב Reducer ונראה איך לצמצם חלק גדול מה Boilerplate והכיעור של רוב ה Reducers במערכת.
## על מה אנחנו מדברים כשאנחנו אומרים Boilerplate
קוד ה Reducer מתוך דוגמת ה Todos בתיעוד, שלצערי הרבה אנשים לוקחים אותו בתור תבנית לאיך Redux צריך להיראות, נראה כך:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
)
default:
return state
}
}
export default todos
המאפיין הבולט של רוב ה Reducers הוא מבנה של switch/case בתוך פונקציה. הפונקציה בודקת מה סוג הפעולה ומחזירה סטייט חדש לפי סוג הפעולה והפרמטרים שלה.
זה מבנה יפה כשמסתכלים עליו מלמעלה אבל השטן הוא בפרטים. ככל שיש יותר פעולות פונקציה כזאת הולך להיות מאוד ארוכה ויהיה קשה לקרוא אותה. הפיתרון המוצע בתיעוד, להפריד את ה Reducer למספר Reducers, הוא לא רלוונטי וברוב המקרים רק הופך את הקוד לקשה יותר לקריאה.
אתגר נוסף בקוד הוא שכל פעם שאנחנו מוסיפים פעולה חדשה אנחנו צריכים לכתוב גם את מימוש הפעולה, אבל גם לעטוף את המימוש בעוד בלוק של case ולהשוות את שם ה Action, מה שעוד יותר מאריך את הפונקציה.
## איך לכתוב פחות קוד
אבל - אם אנחנו מוכנים להתחייב על קונבנציה לשמות של פעולות אפשר לחסוך חלק גדול מהקוד הזה. הנה מה שנעשה:
1. נחליט שכל פעולה תקבל טיפול בפונקציית עזר קטנה.
2. שם הפונקציה ייגזר באופן צפוי וידוע מראש משם הפעולה.
3. נכתוב את כל פונקציות הטיפול בתוך אוביקט ראשי.
4. נכתוב פונקציה שמקבלת את אותו אוביקט ראשי ויוצרת ממנו בצורה אוטומטית Reducer, אבל במקום switch/case היא תגלה לבד את שם הפונקציה לפי ה action.type.
הקוד אחרי השינוי שלי נראה כך:
import createReducer from "./reducerUtils";
const todos = {
addTodo(state, { id, text }) {
return [...state, { id, text, completed: false }];
},
toggleTodo(state, { id }) {
function toggle(todo) {
return { ...todo, completed: !todo.completed };
}
return state.map(todo => (todo.id === id ? toggle(todo) : todo));
}
};
export default createReducer(todos);
רוצים להוסיף פעולה חדשה? אין בעיה פשוט תוסיפו פונקציית טיפול. לא צריך להוסיף בלוק case חדש או להשוות type. דבר נוסף שאני אוהב במבנה כאן הוא ששמות הפרמטרים מופיעים בתוך הגדרת הפונקציה בזכות כתיב ה Destructuring של JavaScript. ואולי היתרון הכי גדול: אני יכול להשתמש באותו שם משתנה בכל אחת מהפונקציות בלי בעיה (בניגוד לבלוק ה switch/case שם כל בלוק case עובד על אותם משתנים כמו כל האחרים).
את רוב הקוד הגנרי העברתי לקובץ reducerUtils בו אפשר להשתמש מכל אחד מה Reducers במערכת:
function convertActionTypeToName(actionType) {
return actionType.toLowerCase().replaceAll(/_(\w)/g, v => v[1].toUpperCase());
}
export default function createReducer(handlers) {
return function reducer(state = [], action) {
const key = convertActionTypeToName(action.type);
const handler = handlers[key];
if (handler) {
return handler(state, action);
} else {
return state;
}
};
}
אתם יכולים לשחק עם הקוד ב Sandbox שהכנתי כאן:
https://codesandbox.io/s/2020-08-reduce-redux-boilerplate-2-88dnv
בחלק הבא בסידרה אדבר על הקומפוננטות ונראה איך לצמצם קוד Boilerplate בתוך קומפוננטה המחוברת ל Redux.1 419
הי חברים לפני כמה ימים כתבתי כאן על פירצת אבטחה באתר הביטוח הלאומי לפי פרסום שהיה באתר הארץ. זה הלינק המתוקן לכתבה:
1 419
# איך לצמצם Boilerplate בקוד Redux באמצעות ES6 Proxy
לפני כמה ימים גדי העיר כאן שאחת הבעיות המרכזיות עם Redux היא כמות קוד ה Boilerplate שצריך לכתוב. לדעתי הקשיים בעבודה עם Redux הם אחרים אבל ההערה הזאת דחפה אותי לשחק קצת עם הכלים כדי לכתוב קוד רידאקס קצר יותר. אחת התוצאות היא התבנית הקצרה הבאה לחסכון בקוד ה Action Creator (אני מקווה בהמשך לפרסם עוד כמה תבניות מועילות ברוח זו).
## על מה אנחנו מדברים כשאנחנו אומרים Boilerplate
הקוד הבא לקוח מתוך דוגמת Redux מתוך תיעוד הספריה:
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const setVisibilityFilter = filter => ({
type: 'SET_VISIBILITY_FILTER',
filter
})
export const toggleTodo = id => ({
type: 'TOGGLE_TODO',
id
})
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
ב Redux כל פעולה גלובאלית שאנחנו רוצים לעשות ביישום מיוצגת על ידי אוביקט Action. הקוד הוא מתוך הקובץ actions/index.js וכל מה שהוא עושה זה להגדיר פונקציות שיוצרות אוביקטים אלה. במילים אחרות בעולם מונחה עצמים רגיל בשביל להוסיף אוביקט Todo היינו יכולים לדמיין קוד כזה שפשוט מפעיל פונקציה:
todos.addTodo(input.value);
ב Redux יש לנו עוד שלב לעבוד: אנחנו צריכים ליצור אוביקט שמייצג את הפרמטרים ואותו להעביר לפונקציה גלובאלית בשם dispatch. הרבה פעמים (כמו בדוגמא מתחילת הפוסט) ניצור פונקציה שיוצרת את האוביקט בשביל לא להתבלבל בשמות הפרמטרים וכך נקבל:
export const addTodo = text => ({
type: 'ADD_TODO',
text
})
dispatch(addTodo(input.value));
השורה האחרונה היא הגיונית וממש דומה לקוד הפעלת פונקציה פשוטה, אבל כל מה שלפניה נקרא Boilerplate. זה קוד שאני כותב אותו רק בשביל ש Redux יהיה שמח.
עכשיו אני יודע מה אתם חושבים - מה הבעיה לוותר על ה Action Creator ופשוט לכתוב קוד שנראה כך:
dispatch({ type: 'ADD_TODO', text: input.value });
אז זה עובד אבל הרבה אנשים מרגישים רע עם סגנון הכתיבה הזה: המילים text ו type יחזרו על עצמן כל פעם שאני מפעיל את הפונקציה, והטקסט ADD_TODO באותיות גדולות לא מסתדר עם איך שאנחנו רגילים לראות שם של פונקציה.
## איך ES6 Proxy יכולים לעזור
דרך אחת ליצור את ה Action Creators בצורה אוטומטית היא להשתמש ב ES6 Proxy. הרעיון הוא לתפוס כל קריאה לאוביקט ה Actions וליצור ממנה אוביקט בצורה דינמית. כך נראה הקוד:
function convertActionNameToType(actionName) {
return actionName.replaceAll(/([A-Z])/g, "_$1").toUpperCase();
}
export const actions = new Proxy(
{},
{
get: function(target, prop) {
if (target[prop] === undefined)
return function(args) {
return {
type: convertActionNameToType(prop),
...args
};
};
else return target[prop];
}
}
);
את זה אפשר לכתוב פעם אחת ביישום ולאורך כל היישום אני יכול עכשיו להשתמש ב actions כדי ליצור אוביקטי פעולה בצורה דינמית לגמרי. כך עשויה להיראות קומפוננטה שמשתמשת באוביקט ה actions שיצרנו:
const AddTodo = ({ dispatch }) => {
let input;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(actions.addTodo({ text: input.value, id: lastId++ }));
input.value = "";
}}
>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
</div>
);
};
כמובן מה שנחמד שעכשיו כל פעם שאני צריך להוסיף Action חדש אני כבר לא צריך להוסיף עבורה Action Creator. כל מה שאני צריך הוא לכתוב את המימוש בתוך ה Reducer ולהתחיל להשתמש ב Action החדש שיצרתי. מצד שני אנחנו נשארים עם התבנית של קריאה לפונקציות בתוך ה Dispatch ואני לא צריך להגדיר לבד אוביקט עם type כל פעם שרוצה לקרוא ל Action.
בפוסטים הבאים בסידרה אני מתכנן לנסות לצמצם Boilerplates נוספים של Redux - למשל את קוד ה Reducer ואת ה mapStateToProps המפורסם.