חישוב נתיב לאלמנט ב React

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

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

1. המשימה: חישוב הנתיב לאלמנט

נתון עץ הפקדים הבא:

<div>
  <input />
  <Thing mark="5">
    <div>
      <OtherThing mark="8">
        <Thing/>
      </OtherThing>
      <Thing>
        <Thing mark="9" />
      </Thing>
    </div>
  </Thing>
</div>

נרצה לאפשר לפקדים Thing ו OtherThing לגשת לערכי mark במסלול המלא עד אליהם, כך שה Thing הראשון יקבל את הערך 5, ה OtherThing שנמצא בתוכו יקבל את הערכים 5 ו-8 וה Thing השלישי את הערכים 5 ו-9.

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

2. ניסיון ראשון: HOC

רעיון אחד לפתרון יכול להיות להעביר את mark כחלק מה prop של כל פקד לילדים שלו, ולעטוף את הסיפור ב Higher Order Component כדי שמי שכותב את הפקדים לא יצטרך לחשוב על זה. הקוד נראה כך:

function withMark(Component) {
  return class extends React.Component {
    render() {
      const previousMarks = this.props.previousMarks || [];
      const combinedMarksForChildren = this.props.mark ? [...previousMarks, this.props.mark] : previousMarks;
      const nextMark = this.props.mark ? [...previousMarks, this.props.mark] : previousMarks;

      return (
        <Component {...this.props} mark={nextMark}>
          {React.Children.map(this.props.children, child => (
            React.cloneElement(child, { previousMarks: combinedMarksForChildren })
          ))}
        </Component>
      );
    }
  }
}

ואפשר לראות את זה בפעולה כאן:

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

3. ניסיון שני: context

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

function withMark(Component) {
  const res = class Marker extends React.Component {
    getChildContext() {
      return {
        marks: this.getMarks(),
      }
    }

    getMarks() {
      const previousMarks = this.context.marks || [];
      return this.props.mark ? [...previousMarks, this.props.mark] : previousMarks;
    }

    render() {
      return (
        <Component {...this.props} mark={this.getMarks()} />
      );
    }
  }

  res.childContextTypes = {
    marks: React.PropTypes.array,
  };
  res.contextTypes = {
    marks: React.PropTypes.array,
  };
  return res;
}

הקוד יותר ארוך אבל עובד ואפשר לראות אותו בפעולה כאן: