Obsługa zdarzeń

Obsługa zdarzeń w Reakcie jest bardzo podobna do tej z drzewa DOM. Istnieje jednak kilka różnic w składni:

  • Zdarzenia reactowe pisane są camelCasem, a nie małymi literami.
  • W JSX procedura obsługi zdarzenia przekazywana jest jako funkcja, a nie łańcuch znaków.

Na przykład, poniższy kod HTML:

<button onclick="activateLasers()">
  Aktywuj lasery
</button>

w Reakcie wygląda nieco inaczej::

<button onClick={activateLasers}>
  Aktywuj lasery
</button>

Kolejna różnica polega na tym, że w Reakcie nie można zwrócić false w celu zapobiegnięcia wykonania domyślnej akcji. Należy jawnie wywołać preventDefault. Na przykład, w czystym HTML-u, aby zapobiec domyślnej akcji linku (otwarciu strony), można napisać:

<a href="#" onclick="console.log('Kliknięto w link.'); return false">
  Kliknij mnie
</a>

W Reakcie, zamiast tego, należy napisać:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('Kliknięto w link.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Kliknij mnie
    </a>
  );
}

Zmienna e to zdarzenie syntetyczne (ang. synthetic event). React tworzy zdarzenia tego typu zgodnie ze specyfikacją W3C, dzięki czemu nie trzeba martwić się o kompatybilność z przeglądarkami. Po więcej informacji sięgnij do specyfikacji obiektu SyntheticEvent.

W kodzie reactowym nie ma potrzeby dodawania obserwatora zdarzenia (ang. event listener) do elementu DOM po jego utworzeniu, poprzez wywoływanie funkcji addEventListener. Zamiast tego, wystarczy przekazać go podczas pierwszego renderowania komponentu.

Gdy komponent definiowany jest przy użyciu klasy ze standardu ES6, często definiuje się procedurę obsługi zdarzenia jako metodę tej klasy. Na przykład, poniższy komponent Toggle wyświetli przycisk, który pozwala użytkownikowi przełączać się między stanami “WŁĄCZONY” i “WYŁĄCZONY”:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // Poniższe wiązanie jest niezbędne do prawidłowego przekazania `this` przy wywołaniu funkcji
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'WŁĄCZONY' : 'WYŁĄCZONY'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

Przetestuj kod na CodePen

Należy zwrócić szczególną uwagę na znaczenie this funkcjach zwrotnych (ang. callbacks) używanych w JSX. W JavaScripcie metody klasy nie są domyślnie dowiązane do instancji. Jeśli zapomnisz dowiązać metodę this.handleClick i przekażesz ją jako atrybut onClick, to this przy wywołaniu będzie miało wartość undefined.

To zachowanie nie jest specyficzne dla Reacta; tak właśnie działają funkcje w JavaScripcie. Generalnie, jeśli odwołujesz się do metody bez () po nazwie, jak na przykład onClick={this.handleClick}, pamiętaj, aby zawsze dowiązywać ją do instancji.

Jeśli denerwuje Cię ciągłe wywoływanie bind, istnieją dwa sposoby na obejście tego problemu. Jeśli używasz eksperymentalnej składni publicznych pól klasy, możesz dowiązać metody do instancji poprzez zadeklarowanie ich jako pola klasy:

class LoggingButton extends React.Component {
  // Poniższy kod wymusza dowiązanie `this` wewnątrz handleClick.
  // Uwaga: to jest składnia *eksperymentalna*.
  handleClick = () => {
    console.log('this ma wartość:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Kliknij mnie
      </button>
    );
  }
}

Powyższa składnia jest domyślnie włączona w Create React App.

Jeśli nie chcesz używać tej składni, możesz skorzystać z funkcji strzałkowej (ang. arrow function):

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this ma wartość:', this);
  }

  render() {
    // Poniższy kod wymusza dowiązanie `this` wewnątrz handleClick.
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Kliknij mnie
      </button>
    );
  }
}

Problem z taką składnią polega na tym, że za każdym razem, gdy LoggingButton jest renderowany, tworzona jest nowa funkcja. W większości przypadków nie ma to większego znaczenia. Jeśli jednak będzie przekazywana do komponentów osadzonych głębiej w strukturze, będzie niepotrzebnie powodowała ich ponowne renderowanie. Zalecamy więc korzystanie ze składni pól klasy lub wiązanie metod w konstruktorze, aby uniknąć tego typu problemów z wydajnością.

Przekazywanie argumentów do procedur obsługi zdarzeń

Dość często, na przykład w pętli, potrzebujemy przekazać do procedury obsługi zdarzenia dodatkowy parametr. Na przykład, jeśli zmienna id zawierałaby identyfikator wiersza w tabeli, można by rozwiązać to na dwa sposoby:

<button onClick={(e) => this.deleteRow(id, e)}>Usuń wiersz</button>
<button onClick={this.deleteRow.bind(this, id)}>Usuń wiersz</button>

Obydwie linie robią to samo, przy użyciu, odpowiednio, funkcji strzałkowej oraz Function.prototype.bind.

W obydwóch przypadkach argument e, reprezentujący zdarzenie reactowe, zostanie przekazany jako drugi w kolejności, zaraz po identyfikatorze wiersza. W przypadku funkcji strzałkowej, musimy przekazać go jawnie, natomiast w bind kolejne argumenty są przekazywane do funkcji automatycznie.