Wprowadzenie do Hooków React

Bartłomiej Szajewski

Zaktualizowaliśmy ten tekst dla Ciebie!
Autor aktualizacji: Rafał Boszkowicz

React 16.8 wprowadził kilka nowych funkcjonalności, w tym tzw. Hooki. Czym one są? Hooki to funkcje dostarczane przez React, które pozwalają nam korzystać z funkcji Reacta bezpośrednio w komponentach funkcyjnych.

Motywacja

W trakcie tworzenia projektów w React, programiści napotykają na wiele poważnych problemów:

Ponowne użycie logiki

Najważniejszym problemem jest brak prostego sposobu na ponowne użycie logiki ze stanem między komponentami. Możesz pamiętać mixiny ze starszych wersji React, które próbowały rozwiązać ten problem, ale miały więcej wad niż zalet, dlatego zostały usunięte. Zamiast tego zespół React wprowadził dwa wzorce – komponenty wyższego rzędu (higher-order components) oraz render props. Niestety, oba wymagają restrukturyzacji komponentów, co zmniejsza czytelność struktury plików, a w DevTools możesz zobaczyć tzw. „wrapper hell”.

Hooki pozwalają na ponowne użycie logiki ze stanem bez zmian w hierarchii komponentów. Zawsze możesz wyodrębnić logikę ze stanem z komponentu, co oznacza, że może być ponownie użyta i testowana niezależnie!

Ogromne komponenty

Jeśli kiedykolwiek pracowałeś nad projektami React, z pewnością wiesz, że nawet proste komponenty z czasem stają się coraz bardziej złożone. Dochodzą nowe wymagania wprowadzające nową logikę (stateful logic), jak i efekty uboczne. Metody związane z cyklem życia komponentu rozrastają się, a nowe metody są dodawane. Dane są pobierane, metody subskrybowane, a zdarzenia wiązane w komponencie componentDidMount. To samo dotyczy componentWillUnmount, gdzie anulowane są oczekujące żądania, metody są odsubskrybowywane, a zdarzenia odłączane, co może powodować wycieki pamięci, jeśli zostanie to pominięte.

Czasami nie jesteśmy w stanie podzielić naszego komponentu na mniejsze, ponieważ złożona logika ze stanem jest trudna do rozdzielenia i przetestowania. Hooki rozwiązują ten problem, umożliwiając podział kodu na mniejsze funkcje w oparciu o to, do czego się odnoszą, np. pobierania danych, konfiguracji subskrypcji lub obsługi zdarzeń.

Niejasne klasy

Klasy są trudne do odczytania przez ludzi, zwłaszcza ze względu na to, że JavaScript znacznie różni się od większości innych języków programowania. Sytuacja staje się jeszcze bardziej skomplikowana, ponieważ trzeba pamiętać o wiązaniu wszystkich metod odwołujących się do this lub o użyciu funkcji strzałkowej w deklaracji.

Okazuje się, że proces minimalizacji klas jest problematyczny. Transpiler nie zna widoczności metod klasy, więc nie może zminimalizować ich nazw, ponieważ mogą być już używane gdzieś poza klasą.

Hooki rozwiązują problem, pozwalając użytkownikowi korzystać z funkcji Reacta bez konieczności tworzenia klas.

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

    this.state = {
      name: "Adam"
    };
    this.onChange = this.onChange.bind(this);
  }

  onChange(e) {
    this.setState({
      name: e.target.value
    });
  }

  render() {
    return <input value="{this.state.name}" onchange="{this.onChange}">;
  }
}

Implementacja kontrolowanego wejścia oparta na hookach:

const App = () => {
  const [name, setName] = useState("Adam");
  const onChange = e => setName(e.target.value);

  return <input value="{name}" onchange="{onChange}">;
};

Po transpilacji i minimizacji:
Klasy:

"use strict";
var _createClass = (function() {
  function e(e, t) {
    for (var n = 0; n < t.length; n++) {
      var r = t[n];
      (r.enumerable = r.enumerable || !1),
        (r.configurable = !0),
        "value" in r && (r.writable = !0),
        Object.defineProperty(e, r.key, r);
    }
  }
  return function(t, n, r) {
    return n && e(t.prototype, n), r && e(t, r), t;
  };
})();
function _classCallCheck(e, t) {
  if (!(e instanceof t))
    throw new TypeError("Cannot call a class as a function");
}
function _possibleConstructorReturn(e, t) {
  if (!e)
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  return !t || ("object" != typeof t && "function" != typeof t) ? e : t;
}
function _inherits(e, t) {
  if ("function" != typeof t && null !== t)
    throw new TypeError(
      "Super expression must either be null or a function, not " + typeof t
    );
  (e.prototype = Object.create(t && t.prototype, {
    constructor: { value: e, enumerable: !1, writable: !0, configurable: !0 }
  })),
    t &&
      (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : (e.__proto__ = t));
}
var App = (function(e) {
  function t(e) {
    _classCallCheck(this, t);
    var n = _possibleConstructorReturn(
      this,
      (t.__proto__ || Object.getPrototypeOf(t)).call(this, e)
    );
    return (n.state = { name: "Adam" }), (n.onChange = n.onChange.bind(n)), n;
  }
  return (
    _inherits(t, React.Component),
    _createClass(t, [
      {
        key: "onChange",
        value: function(e) {
          this.setState({ name: e.target.value });
        }
      },
      {
        key: "render",
        value: function() {
          return React.createElement("input", {
            value: this.state.name,
            onChange: this.onChange
          });
        }
      }
    ]),
    t
  );
})();

Hooki:

"use strict";
var _slicedToArray = (function() {
    return function(r, t) {
      if (Array.isArray(r)) return r;
      if (Symbol.iterator in Object(r))
        return (function(r, t) {
          var e = [],
            n = !0,
            a = !1,
            i = void 0;
          try {
            for (
              var u, o = r[Symbol.iterator]();
              !(n = (u = o.next()).done) &&
              (e.push(u.value), !t || e.length !== t);
              n = !0
            );
          } catch (r) {
            (a = !0), (i = r);
          } finally {
            try {
              !n && o.return && o.return();
            } finally {
              if (a) throw i;
            }
          }
          return e;
        })(r, t);
      throw new TypeError(
        "Invalid attempt to destructure non-iterable instance"
      );
    };
  })(),
  App = function() {
    var r = useState("Adam"),
      t = _slicedToArray(r, 2),
      e = t[0],
      n = t[1];
    return React.createElement("input", {
      value: e,
      onChange: function(r) {
        return n(r.target.value);
      }
    });
  };

Więc co z klasami?

Zespół Reacta nie planuje rezygnacji z klas, więc nie ma potrzeby natychmiastowej migracji do hooków. Obie struktury mogą współistnieć, ale najlepszym pomysłem jest praktyczne stosowanie hooków w nowych komponentach lub projektach.

Podsumowanie
Nie musisz już używać klas, jeśli chcesz tworzyć komponenty utrzymujące stan wewnętrzny. Struktura i wydajność Twojej aplikacji mogą zostać poprawione dzięki używaniu hooków w komponentach funkcyjnych. Nie potrzebujesz już także HOCów ani render propsów, aby tworzyć reużywalne komponenty zapewniające obsługę konkretnej logiki. Problem ten można rozwiązać poprzez połączenie hook  ów i komponentów funkcyjnych. Zapomnijmy więc o klasach i korzystajmy z hooków!

Linki

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami