הצגת עץ באמצעות ריאקט ורדוקס

30/11/2015

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

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

1. הצגת עץ באמצעות ריאקט ורדוקס

לחצו על כפתור הפלוס כדי לראות צמתים נוספים בעץ, ועל טאב JavaScript כדי לראות את הקוד:

See the Pen vNqOaQ by Ynon Perek (@ynonp) on CodePen.

2. ניהול העץ ברדוקס

הקוד מאפשר שני סוגים של פעולות על הצמתים בעץ: פתיחה/סגירה של צומת והוספת צומת חדש. הקוד עבור הפעולות נמצא בתחילת התוכנית ועיקרו בפונקציה:

function reducer(state = Immutable.fromJS(initialState), action) {
  var path;
  switch(action.type) {
    case ADD:
      path = pathForUpdate(action.payload.path);
      return state.updateIn(path,
                            (node) => node.update('childNodes',
                                                  (list) => list.push(generateNode(action.payload, list.size))) );
    case TOGGLE:
      path = pathForUpdate(action.payload);
      return state.updateIn(path, (node) => node.update('expanded', (val) => ! val ));

    default:
      return state;
  }
}

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

3. הצגת העץ בתור פקד ריאקט

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

  renderChild: function(item) {
    return <Node item={item} toggle={this.props.toggle} />
  },
  render: function() {
    var item = this.props.item
    var disabled  = item.get('childNodes').size == 0;
    var collapsed = ! item.get('expanded');
    var text      = disabled ? "" : collapsed ? "+" : "-";

    return <div style={this.divStyle(item)}>
          <button disabled={disabled}
                  onClick={this.toggle.bind(this, item.get('path'))}>{text}</button>
          <span>{item.get('text')}</span>
          {item.get('expanded') ? item.get('childNodes').map(this.renderChild, this) : false }
      </div>
  }

כל רכיב ריאקט מקבל כ Property מצביע לפריט מעץ המידע הרדוקסי (ה store) שנקרא בקוד item. שימו לב ליצירת מערך הילדים: לוקחים את כל הפריטים ממערך הילדים בעץ המידע ועבור כל אחד מהם מיצרים Node נוסף. הרקורסיה עובדת כאן בלי שום מאמץ מצד המתכנת.

4. והיתרון הגדול של רדוקס: ביצועים

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

  shouldComponentUpdate: function(nextProps, nextState) {
    return ! Immutable.is(nextProps.item, this.props.item);
  },

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