הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

ניבים

02/02/2020

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

קחו אפילו דוגמא פשוטה של לולאה. בניב מסוים של JavaScript אנחנו יכולים למצוא את הקוד הבא:

const parent = document.getElementById('parent');
let childNodes = parent.childNodes;
let childNodesCount = childNodes.length;

for (let i=0; i < childNodesCount; i++) {
    console.log(childNodes[i].textContent);
}

בעוד שבפרויקט אחר אנחנו עשויים למצוא את:

const parent = document.getElementById('parent');
let childNodes = parent.childNodes;

for (let i=0; i < childNodes.length; i++) {
    console.log(childNodes[i].textContent);
}

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

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

טיפ פייתון: זיהוי אות באמצעות ביטוי רגולרי

01/02/2020

התמיכה של פייתון בביטויים רגולאריים היא לא רעה אבל גם לא טובה במיוחד. לדברים לוקח זמן להיכנס והיא כמעט תמיד מרגישה קצת מאחורי שפות כמו Ruby או אפילו perl בכל מה שקשור לחיפושים על טקסטים. דוגמא פשוטה לזה היא (חוסר) היכולת לחפש Character Properties ב Unicode.

בעברית Character Properties אומר שאנחנו מחפשים תו לפי מאפיין מסוים, בלי להתחייב לשפה. למשל אני יכול לחפש אות במחרוזת בלי שיהיה אכפת לי אם מדובר באות באנגלית או בעברית. ה Property הוא מאפיין של קבוצת תווים וחיפוש לפיו הוא חוצה שפות. אפשר למצוא רשימה של המון Unicode Properties בקישור הזה: http://unicode.org/reports/tr44/#Properties.

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

In [2]: regex.search(r'\p{L}', 'hello')
Out[2]: <regex.Match object; span=(0, 1), match='h'>

ואנחנו רואים שבמילה hello יש אות. אותו ביטוי מתאים גם לשפות אחרות:

In [4]: regex.search(r'\p{L}', 'שלום')
Out[4]: <regex.Match object; span=(0, 1), match='ש'>

In [5]: regex.search(r'\p{L}', 'привет')
Out[5]: <regex.Match object; span=(0, 1), match='п'>

וכמובן ממשיך לעבוד גם כשעוברים לסימנים (והפעם לא מזהה התאמה כי סימן הקקי המחייך אינו אות):

In [3]: regex.search(r'\p{L}', '💩')

דוגמא קצרה למימוש Web Spider ב Python

31/01/2020

אחד התרגילים שאני אוהב לתת כשאני מלמד Multi Threaded Programming בשפת Python הוא פיתוח של Web Spider - או בעברית כלי שמקבל דף אינטרנט ומתחיל ממנו לחפש ולהוריד את כל דפי האתר. העכביש מתחיל בעמוד הראשון, מחפש שם לינקים לדפים נוספים, מוריד אותם ואז ממשיך לחפש בהם לינקים לדפים נוספים.

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

הנה דוגמא קצרה של פיתרון באמצעות Threads, ואחריה הסבר על הנקודות המרכזיות:

import threading
from threading import Thread, Lock
from queue import Queue, Empty
from urllib.parse import urlparse, urlunparse
from pathlib import Path
from bs4 import BeautifulSoup
import requests

base = 'https://www.python.org'
path = '/dev/peps/'
dest = Path('./pydoc')
found_links = set()
found_links_lock = Lock()
q = Queue()

tls = threading.local()

def run(q):
    tls.session = requests.Session()
    while True:
        href = q.get()
        if href is None:
            return

        with found_links_lock:
            if href in found_links:
                print(f"Found {href} that was already downloaded")
                q.task_done()
                continue

            found_links.update([href])

        discover(href)
        q.task_done()


def download(url, to):
    r = tls.session.get(url, allow_redirects=True)
    open(to, 'wb').write(r.content)


def parse(htmlfile):
    with open(htmlfile) as f:
        soup = BeautifulSoup(f.read(), 'lxml')
        new_links = set([
            a['href'] for a in soup.find_all('a')
            if a['href'].startswith('/dev/peps') and
               a['href'] not in found_links])

        for link in new_links:
            q.put(link)

        print(f"Parse done - added {len(new_links)} new tasks")


def discover(href):
    try:
        url = urlparse(href)
        srcpath = url.path

        if not srcpath.startswith(path):
            print("Path out of scope - ignore: ", href)
            return

        if srcpath.endswith('/'):
            destpath = Path(dest) / Path(srcpath[1:]) / 'index.html'
        else:
            destpath = Path(dest) / Path(srcpath[1:])

        destpath.parents[0].mkdir(parents=True, exist_ok=True)

        if url.netloc == '':
            href = base + href

        download(href, destpath)
        parse(destpath)

    except Exception as e:
        print("Failed to discover href ", href, e)
        raise e


def main():
    found_links.add('/dev/peps/')
    start = 'https://www.python.org/dev/peps/'
    q.put(start)
    workers = [Thread(target=run, args=(q,)) for _ in range(4)]
    for w in workers: w.start()
    q.join()
    for _ in range(4):
        q.put(None)

    for w in workers: w.join()


main()

מה הקוד עושה בגדול:

  1. הפונקציה download מורידה קובץ מהרשת ושומרת אותו. בשביל שהפונקציה תעבוד יחסית מהר אני משתמש ב requests.Session. בגלל ש requests.Session לא עובד טוב בין תהליכונים לכל תהליכון יש Session משלו שנשמר באמצעות Thread Local Storage.

  2. הפונקציה parse מקבלת קובץ html ומחפשת בו קישורים. כל פעם שמצאה קישור חדש היא מוסיפה אותו לתור.

  3. הפונקציה המרכזית היא discover. פונקציה זו לוקחת קישור מהתור, מורידה אותו ומפעילה עליו את parse כדי לחפש בו קישורים.

  4. הפונקציה run מנהלת את התהליכונים והסינכרון ביניהם. כל תהליכון יריץ את פונקציית run ויחכה לפריטים מהתור. הוא יעדכן את התור בחזרה כל פעם שסיים לטפל בפריט (באמצעות discover) וכך הקוד הראשי ידע מתי סיימנו לטפל בכל הפריטים.

  5. בקוד הראשי main אני יוצר את התהליכונים ושולח אותם לדרכם. לאט לאט הם ימשכו קישורים מהתור ויוסיפו קישורים חדשים. הפקודה q.join מחכה עד שהתור יתרוקן וגם כל הקישורים שכעת בעבודה יסתיימו ורק אז חוזרת. שליחת None לכל התהליכונים מוציאה אותם מהלולאה כדי לסיים את התוכנית.

איך להוסיף בדיקות יחידה ליישום React ו Redux

30/01/2020

אתם כבר יודעים כמה אני אוהב בדיקות יחידה ובמיוחד כאלה שרצות אוטומטית כל פעם שאני מנסה לעשות שטויות ולהעלות קוד שבור ל Production. ספריית הבדיקה Jest מבית פייסבוק נועדה לכתיבת בדיקות יחידה ליישומי React ו Redux (למרות שאפשר להשתמש בה כדי לבדוק כל קוד צד-לקוח). כשהם רק פירסמו אותה הספריה היתה מאוד לא בשלה והרבה זמן התרחקתי ממנה והעדפתי את מוקה. היום המצב שונה ואני חושב שכבר אפשר להמליץ עליה ולצרף דף הוראות שיעזור לכם להוסיף אותה ליישומי React ו Redux שלכם.

אבל לפני הכל - מה Jest עוזרת לנו? אז Jest היא Test Runner שזה אומר שהיא לוקחת את הקוד שלכם, שולחת אותו ל Babel ואז מריצה אותו בתוך Node.JS כדי לראות אם הכל עובד כמו שצריך. זה אומר שאתם לא צריכים אפילו לפתוח דפדפן כדי לוודא שלא שברתם שום לוגיקה, ואפשר משורת הפקודה להפעיל את הבדיקה ולקבל:

➜  my-jsoneditor git:(master) npm run test

> my-jsoneditor@1.0.0 test /Users/ynonperek/work/projects/intel/my-jsoneditor
> jest

 PASS  src/redux/reducer.test.js
 PASS  src/label.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.723s
Ran all test suites.

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

ג'סט משתמש ב jsdom כדי לאפשר לכם לכתוב גם קוד שצריך את הרכיבים של הדפדפן ולכן לא היתה לו בעיה להריץ לי למשל בדיקה כמו זו:

test('browser dont work', () => {
  expect(document.body).toBeTruthy();
});

נשמע טוב? בואו נלך להתקין אותו ולכתוב כמה בדיקות.

המשך קריאה

איך להציג שגיאות (ולאפס אותן) בתוך React Router

29/01/2020

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

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

ההצעה מהתיעוד של ריאקט למימוש מחלקה Error Boundary נראית כך:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

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

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

ביישומים שמשתמשים ב React Router אנחנו רוצים לאפס את השגיאה בצורה אוטומטית כשמשתמש עובר לנתיב אחר, כמו שהיה קורה אם המשתמש היה לוחץ על קישור רגיל לדף אחר. שינוי קטן ב Error Boundary יפתור את הבעיה:

  1. אעטוף את המחלקה ב withRouter כדי לקבל גישה למאפיין location.

  2. אוסיף למחלקה מימוש ל componentDidUpdate שיבדוק אם המיקום השתנה.

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

הקוד אחרי התיקון נראה כך:

const ErrorBoundary = withRouter(class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.location !== prevProps.location) {
      this.setState({ hasError: false });
    }
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    // logErrorToMyService(error, errorInfo);
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <div>
          <h1>Something went wrong.</h1>
          <a href='/'>Back home</a>
        </div>
      );
    }

    return this.props.children;
  }
});

עכשיו אני מצליחה!

28/01/2020

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

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

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

מה קרה כשניסיתי לתרגם קוד מ jQuery ל React

27/01/2020

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

// Write React front end to talk to this server
async function refresh() {
    const messages = await $.get('/api/v1.0/messages');
    $('ul').html('');
    $.each(messages, function() {
        $('ul').append(`<li>${this.from} - ${this.text}</li>`);
    });
}

$('form').on('submit', async function(e) {
    e.preventDefault();
    const from = $('input[name="from"]').val();
    const text = $('input[name="text"]').val();
    const msg = { from, text };

    await $.post('/api/v1.0/messages', msg);
    $('ul').append(`<li>${from} - ${text}</li>`);
});

$('#btn-refresh').on('click', refresh);

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

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

בתרגום לריאקט הרבה מהקסם הולך לאיבוד. הנה אותו מנגנון בגירסת React ו TypeScript:

import React, {FormEvent, useState} from "react";
import $ from "jquery";

interface IMessage {
    from: string,
    text: string,
}

type EmptyFunction = () => void;

function ListMessages(props: {refresh: EmptyFunction, messages: IMessage[] }) {
    const { refresh, messages } = props;
    return (
        <>
            <button onClick={refresh}>Refresh</button>
            <ul>
                {messages.map((msg) => (
                    <li>{msg.from} - {msg.text}</li>
                ))}
            </ul>
        </>
    )

}

function NewMessage(props: {send: (from: string, text: string) => void}) {
    const [from, setFrom] = useState('');
    const [text, setText] = useState('');
    const { send } = props;

    async function handleSubmit(e: FormEvent) {
        e.preventDefault();
        await send(from, text);
        setFrom('');
        setText('');
    }

    return (
      <form onSubmit={handleSubmit}>
        <label>
          From:
          <input
              type="text"
              name="from"
              value={from}
              onChange={(e) => setFrom(e.target.value)}
          />
        </label>

        <label>
          Text:
          <input
              type="text"
              name="text"
              value={text}
              onChange={(e) => setText(e.target.value)}
          />
        </label>

        <input type="submit" value="Send" />
      </form>
    )
}

export default function MessagesPage(props: {}) {
    const [messages, setMessages] = useState<IMessage []>([]);

    async function refresh() {
        const res = await $.get('http://localhost:3000/api/v1.0/messages');
        setMessages(res);
    }

    async function send(from: string, text: string) {
        const msg = { from, text };
        await $.post('http://localhost:3000/api/v1.0/messages', msg);
        setMessages([...messages, msg]);
    }

    return (
        <>
            <NewMessage send={send} />
            <ListMessages messages={messages} refresh={refresh} />
        </>
    )
}

במקום לקבל קוד JavaScript נקי יש לנו חיבור בין קוד התצוגה לקוד הלוגיקה. ה HTML הוא ממש בתוך הפרצוף. גם החלוקה בין הקומפוננטות נראית מלאכותית כי ListMessages ו NewMessage לא באמת יכולות לעבוד לבד ותמיד חייבות להופיע בתוך MessagesPage בשביל לקבל את ה props המתאימים.

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

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

פרויקטים שאי אפשר לטעות בהם

26/01/2020

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

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

זד שו עשה את זה בפייתון עם הספר שלו learn python the hard way. בדומה לדואולינגו, הספר זכה להצלחה מסחררת כי אנשים שקראו אותו הצליחו להתחיל לתכנת לפני שלמדו את הכללים, פשוט באמצעות ביצוע פרויקטים שאי אפשר לטעות בהם. אם יום אחד אני אכתוב ספר תכנות למתחילים הוא בוודאות יהיה בגישה זו.

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

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

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

היכולת לרצות

25/01/2020

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

אפילו שבמקומות אחרים ובחברות אחרות דברים עובדים אחרת לגמרי.

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

אתם רואים את זה כשמתכנתת מגיעה מחברה אחרת ומסתכלת בתדהמה על שיטות העבודה שלכם בנושאים מסוימים, ובאימה על שיטות העבודה במקומות אחרים - "למה אין לכם בדיקות אוטומטיות?", "למה ה Deployment שלכם כל כך מסובך?" אבל גם "וואו אתם עובדים פה ב TypeScript!" ו "מה, יש פה קורסים מקצועיים כחלק מהעבודה? מדהים".

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

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

24/01/2020

למרות שאין בעיה להמשיך לעבוד עם 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.

המשך קריאה