3 טיפים קצרים לפיתוח יעיל ב React ו Redux

17/04/2017

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

ה Trade Off של Redux הוא די ברור: ״שלמו״ בפיתוח מסורבל של ה Actions וה Reducers וקבלו בתמורה פיתוח מהיר ויעיל של קוד React. בשביל להנות מהיתרונות בצד של React מומלץ להקפיד לפחות על שלושת הכללים הבאים.

1. פקד אחד בקובץ

מתכנתי Angular יודעים לדקלם את היתרונות של ה Dependency Injection שלהם עוד מגירסת אנגולר 1. גם ב Redux יש DI רק שקוראים לו Webpack. כשטוענים את כל המודולים דרך Webpack קל מאוד להחליף את חלקם או לטעון אותם באופן דינמי. נשווה בין שתי דוגמאות, תחילה שמירת שני פקדים באותו קובץ:

// example 1 - multiple components in one file (Bad)
function Layout(props) {
    return <div>...</div>;
}

function Content(props) {
    return (
        <Layout>
            <p>this is my page</p>
        </Layout>
    );
}

לעומת שימוש ב Webpack והפרדה לקבצים:

// example 2 - multiple files. File content.jsx (Good)
import Layout from './layout';

export default function Content(props) {
    return (
        <Layout>
            <p>this is my page</p>
        </Layout>
    );
}

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

2. העדיפו פקדים פשוטים

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

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

// demo1 - complex components (Bad)
function Layout(props) {
    const { isAdmin } = props;

    return (<div>
        {isAdmin && <a href="/admin">Administration</a>}
        <div className='content'>
            {props.children}
        </div>
    </div>
}

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

// file user_layout.jsx
export default function UserLayout(props) {
    return (
        <div className='content'>
            {props.children}
        </div>
    );
}

// file admin_layout.jsx
export default function AdminLayout(props) {
    return (
        <div>
            <a href='/admin'>Administration</a>
            <div className='content'>
                {props.children}
            </div>
        </div>
    );
}

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

אגב כתיב כזה גם מקל על טעינה דינמית של ה Layout הנכון, ואפשר לקרוא על זה בהרחבה כאן: https://webpack.js.org/guides/lazy-load-react/

3. השתמשו ב State כשצריך

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

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

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

function MyForm(props) {
    return (
    <form>
        <label>
            User Name
            <input type='text' name='name' />
        </label>

        <label>
            <input
                type='checkbox'
                name='details'
                onClick={props.dispatch(actions.toggleShowDetails())}
            />
            Show Details
        </label>

        {props.showDetails && <ExtraFields />}
    </form>
    );
}

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

השוו לקוד הבא שמשתמש ב state:

component MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showDetails: false,
        };
    }

    render() {
    const { showDetails } = this.state;

    return (
        <form>
            <label>
                User Name
                <input type='text' name='name' />
            </label>

            <label>
                <input
                    type='checkbox'
                    name='details'
                    onClick={this.setState({ showDetails: !showDetails })}
                />
                Show Details
            </label>

            {showDetails && <ExtraFields />}
        </form>
    );
    }
}

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

4. וקטנה לסיום

כשאתם שולפים את כל המידע מ props בתחילת פונקציית ה render למשתנים פרטיים של הפונקציה הרבה יותר קל להחליף מ class ל stateless component. נשווה את:

function MyComponent(props) {
    return (<div>
        <p>{props.name}</p>
        <p>{props.email}</p>
    </div>);
}

לעומת:

function MyComponent(props) {
    const { name, email } = props;

    return (<div>
        <p>{name}</p>
        <p>{email}</p>
    </div>);
}

בגירסא השניה כשנרצה להפוך את הפונקציה לפקד class צריך לשנות רק את השורה הראשונה ל this.props. לעומתה בגירסא הראשונה צריך לעבור על כל תוכן הפונקציה ולשנות כל מקום שמופיע props ל this.props (שני שינויים בגירסא הפשוטה שהוצגה, פוטנציאל הרסני הרבה יותר בחיים האמיתיים).