העברת פונקציות לקומפוננטות

איך אני מעביר מטפל אירוע (כמו onClick) לקומפוננטה?

העבר מטפל אירוע ופונקציות אחרות כ-props לקומפוננטות ילדים:

<button onClick={this.handleClick}>

אם אתה צריך גישה לקומפוננטת האב מתוך מטפל האירוע, אתה צריך גם לעשות bind לפונקציה למופע הקומפוננטה (ראה למטה).

איך אני עושה לפונקציה bind למופע של קומפוננטה?

יש כמה דרכים לוודא שלפונקציות יש גישה לתכונות של קומפוננטה כמו this.props ו-this.state, תלוי באיזה תחביר ושלבי בנייה אתה משתמש.

bind בתוך בנאי (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('הכפתור נלחץ');
  }
  render() {
    return <button onClick={this.handleClick}>לחץ עליי</button>;
  }
}

תכונות מחלקה (הצעה בשלב 3)

class Foo extends Component {
  // הערה: תחביר זה ניסיוני ואינו חלק מהתקן בינתיים
  handleClick = () => {
    console.log('הכפתור נלחץ');
  }
  render() {
    return <button onClick={this.handleClick}>לחץ עליי</button>;
  }
}

bind ב-render

class Foo extends Component {
  handleClick() {
    console.log('הכפתור נלחץ');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>לחץ עליי</button>;
  }
}

הערה:

שימוש ב-function.prototype.bind בתוך render יוצר פונקציה חדשה בכל פעם שהקומפוננטה מרונדרת, דבר שיכול לגרום להשלכות על ביצועים (ראה למטה).

פונקציית חץ ב-render

class Foo extends Component {
  handleClick() {
    console.log('הכפתור נלחץ');
  }
  render() {
    return <button onClick={() => this.handleClick()}>לחץ עליי</button>;
  }
}

הערה:

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

האם זה בסדר להשתמש בפונקציות חץ בתוך מתודות render?

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

אם אכן יש לך בעיות ביצועים, בהחלט, בצע אופטימיזציה!

מדוע נחוץ לעשות binding בכלל?

ב-JavaScript, שני קטעי הקוד האלה אינם שווי ערך:

obj.method();
var method = obj.method;
method();

לעשות binding למתודות עוזר לוודא שקטע הקוד השני יעבוד באותה צורה כמו הראשון.

עם React, בדרך כלל עליך לעשות bind רק למתודות שאתה מעביר לקומפוננטות אחרות. לדוגמה, <button onClick={this.handleClick}> מעביר את המתודה this.handleClick כך שתרצה לעשות לה bind. לעומת זאת, אין צורך לעשות bind למתודת render או למתודות מעגל החיים: איננו מעבירים אותן לקומפוננטות אחרות.

הפוסט הזה של Yehuda Katz מסביר מה זה binding, ואיך פונקציות עובדות ב-JavaScript, בצורה מפורטת.

מדוע הפונקציה שלי מקבלת קריאה בכל פעם שהקומפוננטה מרונדרת?

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

render() {
  // טעות: קריאה לפונקציה במקום העברה שלה כרפרנס!
  return <button onClick={this.handleClick()}>Click Me</button>
}

במקום, העבר את הפונקציה עצמה (ללא סוגריים):

render() {
  // נכון: הפונקציה מועברת כרפרנס!
  return <button onClick={this.handleClick}>Click Me</button>
}

איך אני מעביר פרמטר למטפל אירוע או ל-callback?

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

<button onClick={() => this.handleClick(id)} />

זה שווה ערך לקריאה ל-.bind:

<button onClick={this.handleClick.bind(this, id)} />

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

const A = 65 // קוד תו ב-ASCII

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }
  handleClick(letter) {
    this.setState({ justClicked: letter });
  }
  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} onClick={() => this.handleClick(letter)}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

דוגמה: העברת פרמטרים על ידי שימוש במאפייני-מידע (data-attributes)

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

const A = 65 // קוד תו ב-ASCII

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }

  handleClick(e) {
    this.setState({
      justClicked: e.target.dataset.letter
    });
  }

  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} data-letter={letter} onClick={this.handleClick}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

איך אני יכול למנוע מפונקציה להיקרא יותר מדי מהר או יותר מדי פעמים ברצף?

אם יש לך מטפל אירוע כמו onClick או onScroll ואתה רוצה למנוע מה-callback להיקרא מהר מדי, אתה יכול להגביל את הקצב שבו ה-callback מוצא לפועל. זה יכול להעשות על ידי:

  • throttling: דגום שינויים על בסיס תדירות (למשל _.throttle)
  • debouncing: פרסם שינויים לאחר פרק זמן של אי-פעילות (למשל _.debounce)
  • requestAnimationFrame throttling: דגום שינויים על בסיס requestAnimationFrame (למשל raf-schd)

ראה את הויזואליזציה הזאת לשם השוואה בין פונקציות throttle ו-debounce.

הערה:

בכדי לבטל callbacks מעוכבים, _.debounce, _.throttle ו-raf-schd מספקים מתודת cancel. עליך לקרוא למתודה הזאת מתוך componentWillUnmount או לוודא מתוך הפונקציה המעוכבת שהקומפוננטה עדיין מעוגנת (mounted).

Throttle

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

import throttle from 'lodash.throttle';

class LoadMoreButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleClickThrottled = throttle(this.handleClick, 1000);
  }

  componentWillUnmount() {
    this.handleClickThrottled.cancel();
  }

  render() {
    return <button onClick={this.handleClickThrottled}>טען עוד</button>;
  }

  handleClick() {
    this.props.loadMore();
  }
}

Debounce

Debouncing מוודא שהפונקציה לא תוצא לפועל עד שפרק זמן מסוים חלף מאז הפעם האחרונה בה היא נקראה. זה יכול להיות שימושי כאשר אתה צריך לבצע חישוב יקר כלשהו בתגובה לאירוע שעלול להיות משוגר בתדירות גבוהה (למשל אירועי גלילה או הקלדה). הדוגמה למטה עושה debounce לקלט טקסט עם השהייה של 250 מילי שניות.

import debounce from 'lodash.debounce';

class Searchbox extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.emitChangeDebounced = debounce(this.emitChange, 250);
  }

  componentWillUnmount() {
    this.emitChangeDebounced.cancel();
  }

  render() {
    return (
      <input
        type="text"
        onChange={this.handleChange}
        placeholder="חיפוש..."
        defaultValue={this.props.value}
      />
    );
  }

  handleChange(e) {
    this.emitChangeDebounced(e.target.value);
  }

  emitChange(value) {
    this.props.onChange(value);
  }
}

requestAnimationFrame throttling

requestAnimationFrame היא דרך לקביעת תור להוצאה לפועל של פונקציה בתוך הדפדפן בזמן האופטימלי מבחינת ביצועי רינדור. פונקציה שהוכנסה לתור עם requestAnimationFrame תבוצע בפריים הבא. הדפדפן יעבוד קשה בכדי לוודא שישנם 60 פריימים לשנייה (60 fps). יחד עם זאת, אם הדפדפן לא מסוגל לעשות זאת, הוא יגביל באופן טבעי את כמות הפריימים בשנייה. לדוגמה, מכשיר מסוים עלול להיות מסוגל להתמודד רק עם 30 פריימים לשנייה כך שתקבל רק 30 פריימים באותה שנייה. שימוש ב-requestAnimationFrame בשביל לבצע throttling היא טכניקה שימושית בכך שהיא מונעת ממך לבצע יותר מ-60 עידכונים בשנייה. אם אתה מבצע 100 עידכונים בשנייה זה יוצר עבודה נוספת לדפדפן שהמשתמש בכל מקרה לא ייראה.

הערה:

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

import rafSchedule from 'raf-schd';

class ScrollListener extends React.Component {
  constructor(props) {
    super(props);

    this.handleScroll = this.handleScroll.bind(this);

    // צור פונקציה חדשה לקביעת עידכונים.
    this.scheduleUpdate = rafSchedule(
      point => this.props.onScroll(point)
    );
  }

  handleScroll(e) {
    // כאשר אנחנו מקבלים אירוע גלילה, קבע עידכון.
    // אם אנחנו מקבלים עידכונים רבים במשך פריים אחד, נפרסם רק את הערך האחרון.
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
  }

  componentWillUnmount() {
    // בטל את כל העידכונים הממתינים מפני שאנחנו לא מעוגנים (unmounted).
    this.scheduleUpdate.cancel();
  }

  render() {
    return (
      <div
        style={{ overflow: 'scroll' }}
        onScroll={this.handleScroll}
      >
        <img src="/התמונה-הענקית-שלי.jpg" />
      </div>
    );
  }
}

בדיקת הגבלות קצב

כאשר אתה בודק שקוד הגבלות הקצב עובד בצורה נכונה, היכולת להריץ זמן קדימה עוזרת. אם אתה משתמש ב-jest אתה יכול להשתמש ב-mock timers בשביל להריץ קדימה זמן. אם אתה משתמש ב-throttling עם requestAnimationFrame ייתכן שתמצא ש-raf-stub הוא כלי שימושי לשליטה בתקתוק הפריימים של אנימציות.

האם התוכן בעמוד זה היה שימושי?לעריכת העמוד הנוכחי