איך להפוך Class Component ל Function Component ב React

24/01/2020

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

למרות שאין בעיה להמשיך לעבוד עם Class Components, קורה לפעמים שנמצא דוגמא ברשת של Class Component ונעדיף להפוך אותה ל Function Component לפני שמשלבים אותה ביישום שלנו.

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

https://github.com/josdejong/jsoneditor/tree/develop/examples/react_demo

והקובץ המרכזי ממנה הוא הפקד הבא:

import React, {Component} from 'react';

import JSONEditor from 'jsoneditor';
import 'jsoneditor/dist/jsoneditor.css';

import './JSONEditorDemo.css';

export default class JSONEditorDemo extends Component {
  componentDidMount () {
    const options = {
      mode: 'tree',
      onChangeJSON: this.props.onChangeJSON
    };

    this.jsoneditor = new JSONEditor(this.container, options);
    this.jsoneditor.set(this.props.json);
  }

  componentWillUnmount () {
    if (this.jsoneditor) {
      this.jsoneditor.destroy();
    }
  }

  componentDidUpdate() {
    this.jsoneditor.update(this.props.json);
  }

  render() {
    return (
        <div className="jsoneditor-react-container" ref={elem => this.container = elem} />
    );
  }
}

הפקד כולל לא מעט פונקציות מחזור חיים ולכן אני חושב שיהיה מעניין להפוך אותו ל Functional Component ולראות בדרך כמה תבניות של React Hooks.

1. הפונקציה componentDidMount

הפונקציה הראשונה היא componentDidMount וכדי לבנות אותה בפקד מסוג פונקציה אנחנו משתמשים ב useEffect עם מערך תלויות ריק. לכן נקודת ההתחלה שלי לפקד הפונקציונאלי עשויה להיראות כך:

export default function JSONEditorDemo(props) {
  useEffect(function() {
    const options = {
      mode: 'tree',
      onChangeJSON: this.props.onChangeJSON
    };

    this.jsoneditor = new JSONEditor(this.container, options);
    this.jsoneditor.set(this.props.json);
  }, []);
}

2. משתני מחלקה this

זה לא עובד כמובן כי יש לנו שני משתנים ששמורים על this: המשתנה container שמייצג את ה DOM Element עבור העורך והמשתנה jsoneditor המייצג את העורך עצמו.

משתנים שב Class Component נשמרו על this אנחנו הופכים למשתנים סטטיים עם הפונקציה useRef. אם צריך לחבר אותם ל DOM Element נוכל להעביר אותם בתור הערך של מאפיין ref של האלמנט. לכן הצעד הבא בשכתוב עשוי להיראות כך:


export default function JSONEditorDemo(props) {
  const { json } = props;
  const jsoneditor = useRef(null);
  const container = useRef(null);

  useEffect(function() {
    const options = {
      mode: 'tree',
      onChangeJSON: this.props.onChangeJSON
    };

    jsoneditor.current = new JSONEditor(container, options);
    jsoneditor.current.set(json);
  }, []);
}

וזה כבר מקרב אותנו למשהו שעובד כיוון שאין this וכל המשתנים מוגדרים.

3. הפונקציה componentWillUnmount

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


export default function JSONEditorDemo(props) {
  const { json } = props;
  const jsoneditor = useRef(null);
  const container = useRef(null);

  useEffect(function() {
    const options = {
      mode: 'tree',
      onChangeJSON: this.props.onChangeJSON
    };

    jsoneditor.current = new JSONEditor(container, options);
    jsoneditor.current.set(json);

    return new function unmount() {
      if (jsoneditor.current) {
        jsoneditor.current.destroy();
      }
    }
  }, []);
}

4. הפונקציה componentDidUpdate

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

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

  useEffect(function() {
    this.jsoneditor.update(this.props.json);
  }, [props.json]);

5. פונקציית render

נשאר רק להעתיק את הערך שחזר מהפונקציה render לתוך פונקציית הפקד שלנו, ולהתאים את הערך שעבר ל ref:

function JSONEditorDemo(props) {
  const { json } = props;
  const jsoneditor = useRef(null);
  const container = useRef(null);

  useEffect(function() {
    const options = {
      mode: "tree",
      onChangeJSON: this.props.onChangeJSON
    };

    jsoneditor.current = new JSONEditor(container, options);
    jsoneditor.current.set(json);
    return function cancel() {
      if (jsoneditor.current) {
        jsoneditor.current.destroy();
      }
    };
  }, []);

  useEffect(
    function() {
      jsoneditor.current.update(this.props.json);
    },
    [props.json]
  );

  return <div className="jsoneditor-react-container" ref={container} />;
}

מוזמנים לראות את הקוד בפעולה בסנדבוקס הבא: https://codesandbox.io/s/silly-feather-xrzh9