Wady React Hooks

Ten wpis jest 40 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Od wielu tygodni nie opada kurz po zamieszaniu związanym z React Hooks. Poziom hype przebił wszelki hajpometry, a rozsądna debata na temat kodu została zastąpiona prześciganiem się w pisaniu co raz to sprytniejszych i czystszych (w sensie pure) reReact Hooków. Zastanówmy się jednak nad wadami tego rozwiązania.

Dlaczego tak działamy i po raz n-ty dajemy się ponieść emocjom, mimo tego, że przecież na podstawie wielu doświadczeń mamy świadomość, że to zgubne? Tego nie wiem i na to pytanie nie będę w stanie udzielić odpowiedzi. Uznałem jednak, że radosnemu uniesieniu na temat Hooków przyda się zdroworozsądkowe podejście i szczypta krytyki. A może bardziej pięść w brzuch.

Po co nam React Hooks?

Po co nam hooki? Żeby ułatwić wprowadzenie Suspense, żeby ludzie nie robili głupich rzeczy, żeby nie było mutacji, żeby kod się lepiej minifikował, żeby można było wydzielać logikę zawierającą stan poza komponenty. Zalety te opisywałem w artykule React Hooks — wprowadzenie i motywacja. Jeśli spojrzysz na to z dystansu, to dostrzeżesz, że React, niczym socjalizm, świetnie rozwiązuje problemy, które sam stworzył.

Reaktywność i mutacje

Głównym argumentem za unikaniem mutacji w React jest utrata spójności pomiędzy interfejsem, a danymi. To brzmi świetnie i bez kontekstu tylko bym przytakiwał. Mutacje są be i nie lubię mutacji. Tyle, że inne frameworki pokazują, że wcale nie musi tak być. Knockout, Angular, Vue.js, Svelte dowodzą, że idea reaktywności i sprytnych mutacji działa lepiej, jest bardziej przystępna i zrozumiała dla ludzi, niż całkowita czystość, do której dąży React. „Czystość”, huh? Wrócimy do tego.

Bardzo znaną biblioteką do zarządzania stanem w React jest MobX. Dlaczego stał się tak popularny? Bo pozwala na mutacje i nie straszy negatywnymi konsekwencjami. Ludzie lubią mutacje. Ludzie chcą mutować dane. Dajcie ludziom mutacje w kontrolowanym środowisku, jak MobX, jak Vue.js, a nie zrobią sobie krzywdy i będą mega produktywni. Tak, pisze to człowiek, który absolutnie uwielbia paradygmaty programowania funkcyjnego. Ale nader wszystko lubię pragmatyzm.

Czystość

Czystość funkcji jest często wskazywana jako ogromna zaleta Reacta. Tyle, że komponenty funkcyjne korzystające z Hooków wcale nie są pure functions. To kłamstwo, które zostało bezmyślnie powtórzone przez tysiące osób, ale mimo to nie stało się prawdą.

Czysta funkcja to taka, która dla tych samych danych zawsze zwraca ten sam rezultat, a jej wynik zależy wyłącznie od przekazanych do niej argumentów. Pure function jest na przykład const fn = x => x + 2;, a nie jest const fn = x => x + Math.random();. Komponenty funkcyjne używające React Hooks nie są więc pure, bo ich wynik zależy właśnie od Hooków!

Autorzy później poprawili się, że chodziło o czystość tylko w pewnym sensie. Jakim? Czy to ułatwia, czy utrudnia zrozumienie Hooków osobom zaprzyjaźnionym z programowaniem funkcyjnym? A wszystkim pozostałym? Znowu: nie jestem w stanie odpowiedzieć na te pytania, ale sądzę, że powstałe zamieszanie w nazewnictwie nie pomaga nikomu.

Krytyka React Hooks

Skoro już jesteśmy przy nazwach, i tak, wiem, to w sumie nieistotne, nazwy można zmienić, bla bla bla… Srsly, useEffect? Chodzi o efekt algebraiczny? Efekt uboczny? O co chodzi? Ani o jedno, ani o drugie, a o funkcję wywoływaną przy każdym renderze! Szybko powstała paczka, która tę niejasną nazwę zmienia i rozbija na 3 hooki: useDidMount, useDidUpdateuseWillUnmount, bo tak naprawdę tym właśnie jest useEffect, a nie żadnym „efektem”.

Żeby było bardziej myląco, to efekty algebraiczne w React też są. Na tyle, na ile pozwala na to sam język, ale są. Rzucanie wyjątku z Promisem w środku w renderze, jak głupio by nie brzmiało, to właśnie Reactowa próba implementacji efektów algebraicznych.

Subtelne bugi

Autorzy Hooków mówią, że Hooki pozwalają uwolnić się od subtelnych bugów w aplikacjach, takich, jak np. zapominanie o pozbyciu się subskrypcji, gdy komponent jest usuwany z drzewa. To półprawda. Czyli, że kłamstwo. Ten problem istnieje, ale Hooki go nie rozwiązują. Robiłem code review wielu fragmentów kodu, w których autorzy zapominali usunąć subskrypcje w useEffect, pomimo Hooków, pomimo dokumentacji, która co rusz o tym wspomina.

class Component1 extends React.Component {
  componentDidMount() {
    this.props.subscribe();
  }
}

function Component2({subscribe}) {
  React.useEffect(() => {
    subscribe();
  });
}

W powyższym przykładzie oba komponenty mają ten sam bug. Co gorsza, komponent funkcyjny ma również drugi błąd. Potrafisz dostrzec, jaki?

Czy lepszą dokumentacją, ewangelizacją albo zasadami ESLint można naprawić ten problem? Pewnie tak. Ale wtedy to nie ma absolutnie żadnego związku z Hookami.

Refy

Każdy, kto próbuje używać Hooków w końcu natknie się na ten sam problem: Bardzo prosty kod po przeniesieniu z klasy do funkcji działa niepoprawnie, ale w niezwykle subtelny sposób. Dobrym przykładem jest jakiekolwiek wywołanie asynchroniczne i odnoszenie się do props: w komponentach klasowych zawartość props będzie zawsze aktualna, nawet asynchronicznie po jakimś czasie. Natomiast w funkcyjnych będą to propsy z momentu wywołania asynchronicznej operacji. Jeśli pomiędzy wywołaniem, a zakończeniem działania propsy się zmienią, to w przypadku komponentu funkcyjnego nasza asynchroniczna funkcja tego nie zobaczy.

Jest to zrozumiałe i wynika z prostej różnicy pomiędzy komponentami funkcyjnymi, a klasowymi: W funkcyjnym render polega na wywołaniu ponownie funkcji, a więc przekazaniu zupełnie nowego obiektu props jako argument. Asynchroniczny callback trzyma jednak referencję do poprzedniego obiektu props, a nie tego nowego. Komponenty klasowe tego problemu nie mają, bo, uwaga, jest tam ukryta mutacja! Tak. this jest mutowalne i React je mutuje podmieniając stare this.props na nowe this.props. Tym sposobem asynchroniczny callback zawsze może się odwołać do aktualnych propsów.

class Component1 extends React.Component {
  componentDidMount() {
    this.props.subscribe(() => {
      console.log(this.props.value);
    });
  }
}

function Component2({subscribe, value}) {
  React.useEffect(() => {
    subscribe(() => {
      console.log(value);
    });
  });
}

Powyższy komponent funkcyjny, oprócz znanych nam już dwóch bugów, ma też nowy błąd: Odwołuje się do nieaktualnych propsów (stale props). Żeby popełnić taką pomyłkę w komponencie klasowym, trzeba się naprawdę natrudzić.

Teraz, jak rozwiązać ten problem w Hookach? Użyć mutowalnych refów. Ta mutacja, która do tej pory była ukryta, kontrolowana i działa się samoistnie (robił ją za nas React) teraz będzie musiała być robiona ręcznie, w sposób niekontrolowany, ze wszystkimi idącymi za tym zagrożeniami i wadami. Wow, to jest pewnie ta reklamowana czystość.

Eslint

ESLint miał naprawić wszystko. Rzeczywiście, czasem jego podpowiedzi do Hooków są naprawdę sprytne. Ale częściej nie. Wziąłem kod powyżej i doprowadziłem do stanu używalności (naprawiłem jeden bug). Hook wygląda teraz tak:

React.useEffect(() => {
  subscribe(() => {
    setValues(vals => [...vals, value]);
  });
}, []);

Efekt to aplikacja, która pozornie działa, ale nadal ma najważniejszy bug: odczytuje nieaktualne już propsy. Oczekiwanym efektem jest to, że oba komponenty renderują tę samą listę.

Ale, ale, chwila, moment, eslint daje mi jakieś ostrzeżenie!

React Hook React.useEffect has missing dependencies: ‚subscribe’ and ‚value’. Either include them or remove the dependency array. If ‚subscribe’ changes too often, find the parent component that defines it and wrap that definition in useCallback.

No dobra, dodajmy subscribevalue do tablicy. Co się wtedy stanie? Ostrzeżenie zniknęło. Kod wygląda tak, jak widać poniżej. Ale czy działa?

React.useEffect(() => {
  subscribe(() => {
    setValues(vals => [...vals, value]);
  });
}, [subscribe, value]);

Co się dzieje? Jest tylko gorzej! Nie ma ostrzeżeń. Kod źle działa. Eslincie, miałeś byś taki mądry 🤔 Kod dostępny live tutaj: https://codesandbox.io/s/divine-violet-z0uyq

Powyższy fragment jest uproszczoną wersją autentycznego kodu jednej z osób, której robiłem code review. I tak, nie ufała mi, kiedy mówiłem, żeby przestała ślepo słuchać eslinta, bo bug jest gdzieś indziej. Nie uwierzyła, że musi użyć refa. Poszła szukać rozwiązania poza Type of Web. W końcu: przepisała z powrotem na klasę.

useCallback, useMemo

Ktoś zwrócił mi uwagę, że znęcam się tylko na useEffect, który jest przecież najtrudniejszym z hooków. Racja. Porozmawiajmy o pozostałych.

useCallbackuseMemo – funkcje potrzebne tylko dlatego, bo React Hooks tworzy więcej problemów, niż rozwiązuje. Spójrzmy na przykład klasy:

class Component3 extends React.PureComponent {
  render() {
    function doSomething() {
      //…
    }

    return <AnotherComponent onSth={doSomething} />
  }
}

Mamy tutaj buga: przy każdym renderze tworzona jest nowa funkcja doSomething, więc komponent AnotherComponent będzie się przerenderowywał zawsze, nawet jeśli de facto to będzie dokładnie taka sama funkcja. Rozwiązania są dwa, ale jedno banalne: Nie tworzyć funkcji w renderze!

class Component3 extends React.PureComponent {
  doSomething = () => {
    // … this.props …
  }
  render() {
    return <AnotherComponent onSth={this.doSomething} />
  }
}

Dzięki temu zawsze odwołujemy się do tej samej referencji do tej samej funkcji i komponent AnotherComponent nie musi się bez sensu przerenderowywać. Jak to wygląda z hookami?

function Component4(props) {
  function doSomething() {
    // … props …
  }

  return <AnotherComponent onSth={doSomething} />
}

Mamy buga. Jak go rozwiązujemy? Używając hooka useCallback!

function Component4(props) {
  const doSomething = React.useCallback(() => {
    // … props …
  }, [/* tu podajemy te propsy, których używamy */]);

  return <AnotherComponent onSth={doSomething} />
}

To nieco zaciemnia kod. A teraz zdaj sobie sprawę z tego, że React.useCallback musisz opakować każdą funkcję, którą przekazujesz do innego komponentu. Każdą jedną. I w każdej z nich pilnować tablicy zależności. A jeśli w funkcji znajdą się operacje asynchroniczne? Jesteś skazana/y na Refy!

Jeszcze jedno, o czym wcześniej nie wspomniałem: Normalnie w aplikacji większość komponentów klasowych dziedziczy po React.PureComponent. Zamiana React.PureComponent na React.Component i odwrotnie to tylko kilka znaków, nie zaciemnia kodu w ogóle. W przypadku Hooków, niestety, musimy cały komponent opakować w React.memo. A więc tak naprawdę powyższy kod wyglądałby o w ten sposób:

const Component4 = React.memo((props) => {
  const doSomething = React.useCallback(() => {
    // … props …
  }, [/* tu podajemy te propsy, których używamy */]);

  return <AnotherComponent onSth={doSomething} />
});

Według mnie to ogromny narzut mentalny i zaciemnienie kodu.

Błędy

Autorzy Hooków mówią, że Hooki pozwalają uwolnić się od subtelnych bugów w aplikacjach, ale nie wspominają, że przynoszą one jeszcze więcej miejsc, w których takie błędy potencjalnie można zrobić. Co gorsza, trudno na pierwszy rzut oka dostrzec, co jest z takim kodem nie tak, a jeszcze trudniej znaleźć rozwiązanie. O ile w przypadku klas każdy jest w stanie wykombinować lepszy lub gorszy sposób, tak w przypadku Hooków jesteśmy skazani na jedną słuszną pseudo-funkcyjną metodę rozwiązywania problemów. Przypomina mi się konwersacja, którą niedawno miałem z koleżanką:

– Zobacz, jaki napisałem Hook, ale super, prawda?
– No, wygląda nieźle. Ale niestety w takim i takim bardzo rzadkim przypadku nie zadziała.
– Uh. Okej. (…) Przepisałem to na klasę, zobacz.
– Znacznie prościej.

Jeśli mi nie wierzysz, to zaimplementuj hook useInterval, który wywołuje przekazaną funkcję co jakiś czas. Bez googlania. W klasie to przecież banał, nie? W Hookach nie powinno to być dużo trudnie… OŻ W MORDĘ.

Klasa. Trochę sobie utrudniłem dodając reagowanie na zmianę propsa time powodujące ustawienie nowego interwału:

class Component5 extends React.Component {
  timerId = 0;

  startInterval = () => {
    clearInterval(this.timerId);
    this.timerId = setInterval(() => this.props.callback(123), this.props.time);
  }

  componentDidMount() {
    this.startInterval();
  }

  componentWillUnmount() {
    clearInterval(this.timerId);
  }

  componentDidUpdate(props) {
    if (this.props.time !== props.time) {
      this.startInterval();
    }
  }
}

Wydaje mi się, że ten kod jest w stanie przeczytać i zrozumieć każda osoba, która miała do czynienia z jakimkolwiek językiem programowania.

Funkcja:

function useInterval(callback, delay) {
  const savedCallback = React.useRef();

  // Remember the latest callback.
  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  React.useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

function Component6({ callback, time }) {
  useInterval(callback, time);
}

useRef? Dwa useEffect? Ale dlaczego? Czy Twoje rozwiązanie przypomina powyższe? Ten kod wziąłem z bloga Dana Abramova. Sam bym pewnie go napisał, ale zajęłoby to tydzień. Nie można jakoś tak… łatwiej? Można! Klasą.

Reużywalność logiki

Teraz na pewno ktoś rozpocznie linię argumentacyjną pod tytułem npm install use-interval. Hooki można łatwo wydzielać i przenosić. Łatwiej, niż analogiczny kod z klasy, to prawda. Tylko co z tego? Ile kodu jesteś w stanie zainstalować z npm? Czy weryfikujesz jego poprawność? Czy autorki i autorzy paczek na npm nie popełniają błędów? O ile łatwiej jest popełnić taki lub podobny, bardzo subtelny błąd w Hookach? Poza tym, nieco mniej wygodny, ale równie „reużywalne” fragmenty kodu można tworzyć używając HoC lub render propsów.

Po co nam Hooki

Każdy zna taką osobę, która próbuje realizować jakiś projekt poboczny w 100% podążając za jednym paradygmatem albo w jednym ezoterycznym języku, no nie? Trochę się podśmiewamy z braku realistycznego podejścia do życia, ale też odczuwamy podziw do oddania się ideom. Każdy lubi się pochwalić napisaniem super skomplikowanego kodu, który jednak jest czysty i zgodny z jakimiś pryncypiami.

No i dokładnie takie są moje odczucia względem React Hooks. Tak, jakbym patrzył na kod tego rąbniętego znajomego, który wszystko pisze w Haskellu i podtyka innym pod nos mówiąc „pochwal mnie, jaki jestem mądry”. Dokładnie tak się czuję, gdy patrzę na React Hooks.

Wiele osób przychodzi do mnie, żeby pokazać mi Hooki, które zostały przez nie napisane. Hooki, które rozwiązują naprawdę banalne problemy, ale z powodów opisanych wcześniej, sam kod jest skomplikowany i zawoalowany. useInterval. useDebounce. Gdyby ten sam problem rozwiązały przy pomocy klasy, nigdy nie przyszłyby się chwalić, bo napisanie klasy nie wzbudzałoby takiego podziwu, gdyż byłoby łatwe, proste, przyjemne i czytelne. Wow, hook, wow, pure, coś tam coś tam algebraic effect coś tam, wow. Chcę zapytać: But why?

Postęp

Niektórzy mówią mi, że mam rację, ale nie da się inaczej. Że tak jest dobrze. Że lepiej, niż było. Że to postęp. Żebym spojrzał z szerszej perspektywy na zalety React Hooks.

Wzdycham wtedy i mówię: Moi drodzy, nie urodziłem się wczoraj. Lata temu z zacięciem broniłem rozwiązań, które wydawały mi się rewolucyjne, bo sądziłem, że nie da się tego samego problemu rozwiązać lepiej. Przykładem niech będzie router Angulara i dynamiczne ładowanie komponentów. Wtedy zdawało mi się, że nie da się lepiej. Dzisiaj wiem, że byłem krótkowzroczny, a prostsze i jednocześnie bardziej elastyczne rozwiązania były w zasięgu ręki. Da się. Zawsze się da.

Czasem potrzeba stworzyć potworka, żeby ktoś ze społeczności powiedział „mam lepszy pomysł”, a frustracja przy pracy z potworkiem popchnęła większą rzeszę ludzi do spróbowania tego lepszego pomysłu. Trzeba tylko zrobić krok wstecz.

Podsumowanie

Czy Hooki rozwiązują te problemy, których rozwiązanie obiecali nam twórcy? Trochę. Wprowadzają też sporo nowych kłopotów. Pytam więc: czy da się lepiej, a przede wszystkim prościej? Wygląda na to, że tak. Spójrzmy na Svelte. Spójrzmy na Vue.js 3. A potem spójrzmy na Reacta. Coś zdecydowanie poszło nie tak.

A jakie jest Twoje zdanie? Napisz koniecznie w komentarzach!

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
47 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Roman Batsenko
7 miesięcy temu

Hej, dzięki za ciekawy artykuł!

Mała poprawka – do komponentów używamy React.memo, a useMemo do „ciężkich” obliczeń 😉
https://reactjs.org/docs/react-api.html#reactmemo

Robert
7 miesięcy temu

Bardzo potrzebny głos w dyskusji. Takie teksty zawsze są przydatne w momentach niebezpiecznie rosnącego hype-driven-developmentu; tak dla przypomnienia, że to tylko kolejny rodzaj młotka. 😉

Przemek Smyrdek
Przemek Smyrdek
7 miesięcy temu

Świetne podsumowanie – dla kilkuletniego Angularowca i przede wszystkim – front-endowca – hooks to jest niestety nieporozumienie od strony DX. Niestety, pokazuje też najgorszą moim zdaniem cechę środowiska Reactowego (o której powstała książka „Nowe szaty króla”).

Przerzuć to proszę na angielski jak znajdziesz czas, bo chętnie poczytałbym odpowiedzi fanów tego podejścia na ten artykuł.

Adam
Adam
7 miesięcy temu

ESLint nie pomoże gdy autor nie potrafi korzystać z useState.

https://codesandbox.io/s/distracted-wright-fh48t

Adam
Adam
7 miesięcy temu

W artykule (i podlinkowanym sandboxie) wykorzystujesz (błędnie) useState.

setValues(vals => [...vals, value]);

Dźwiedziu
Dźwiedziu
7 miesięcy temu

useState to nie to samo co this.setState w komponencie klasowym. useState masz osobne do każdej zmiennej. W ilu miejscach w kodzie zmieniasz jedną zmienną na podstawie jej poprzedniej wartości?

Dźwiedziu
Dźwiedziu
6 miesięcy temu

A da się ten przykład zrobić poprawnie na hook’ach?

W ogóle to:comment image
Zwiesza mi się po wyświetleniu 15 wartości. Niektóre się powtarzają. Zarówno w klasowym jak i w funkcyjnym. Co oto się dzieje? Jakiś memory leak?

Dźwiedziu
Dźwiedziu
6 miesięcy temu

Zrobiłem bez ref’ów i działa:
https://codesandbox.io/s/priceless-surf-pz6h0

Jedynie co zrobiłem to zaimplementowałem unsubscribe. Natomiast fakt, wywoływanie w kółko unsubscribe/subscribe jest tu niepotrzebnym narzutem.

Czyli właściwie z punktu widzenia poprawności (nie wydajności) we wszystkich tych próbach naprawy błędem było wielokrotne dopisanie się do subscribe bez usunięcia po sobie starej?

Dźwiedziu
Dźwiedziu
6 miesięcy temu

eslint z exhaustive-deps i prettier w WebStorm jeszcze lepsze jaja potrafi zrobić. Mianowicie sam wypełnić deps wszystkim, co jest używane w useEffect. Kiedyś zrobiłem sobie forka tego: https://github.com/joepuzzo/informed (początek lipca 2019). Puściłem elegancko prettier i przeglądarka się zawiesiła 😉 okazało się, że przyczyną było wywołanie się useEffect, która miała za dużo w deps i wywoływała się w efekcie przy każdym renderze. A ona znowu coś tam zmieniała i to wymuszało kolejny render i tak szedł w kółko. https://github.com/joepuzzo/informed/blob/40efaccf1cf4b00019a589735e45b25186d76fb0/src/hooks/useField.js – gość nawet okomentował to „// This is VERYYYY!! Important!”, że deps mają być takie, a nie inne.

Pytanie moje, czy w takim razie taki useEffect, który używa tego co jest zmienne i nie ma tego w deps, jest w ogóle poprawny? Nie powinno się własnie mutowalnego refa użyć?
Ta biblioteka w wersji 1.x robiła to wszystko na HoC. Potem w 2.x przepisał wszystko na hook’i.

Adam
Adam
7 miesięcy temu

Komponent, który ma służyć jako przykład jak łatwo jest pisać komponenty klasowe nie działa.

https://codesandbox.io/s/ecstatic-euclid-4lve6

Brakuje w nim funkcji componentDidMount() { this.startInterval() }

Brakuje też componentWillUnmount() { clearInterval(this.timerId) } jeżeli ma działać tak jak hook useInterval.

Poza tym ma buga: przy pierwszym renderze usuwa interval o id 0 (jeśli gdzie indziej w aplikacji tworzone są intervale to niespodziewanie jeden z nich zniknie).

Adam
Adam
7 miesięcy temu

A teraz zdaj sobie sprawę z tego, że w React.useCallback musisz opakować każdą funkcję, którą przekazujesz do innego komponentu. Każdą jedną.

To zły pomysł. W większości przypadków dodawanie useCallback tylko spowolni kod.

https://kentcdodds.com/blog/usememo-and-usecallback

Modzio
Modzio
7 miesięcy temu

Jak najbardziej to prawda, w najnowszych wersjach reacta re-render nie jest problemem bo jest szybki i nie trzeba wszystkiego opakowywać w useCallback czy useMemo, sami autorzy o tym mówią a znaleźć też można wiele artykułów na ten temat, które sprawdzają wydajność i mówią wprost że rzadko kiedy useCallback trzeba użyć 🙂

Marcin
Marcin
7 miesięcy temu

Np tutaj https://github.com/facebook/react/issues/16437, rerender nie jest zly jest jest „cheap” a przewaznie jest w innych przypadkach dopiero MUSISZ uzyc useCallback, raczej optymalizujemy gdy widziny problem a nie na wszelki wypadek

Adam
Adam
7 miesięcy temu

PS Wrzuciłem wcześniej link do poprawionego setState. Tak wygląda pełna implementacja subskrypcji za pomocą hooków.

https://codesandbox.io/s/elated-browser-7r7k7

Modzio
Modzio
7 miesięcy temu

A ja mam wrażenie że ten artykuł jest wynikiem jakiejś niewiedzy, obrazy na React 🙂 Czekam na wersję angielską żeby obiegła świat 🙂

Argumenty np takie odnośnie reużywalności i tego że hooki pomagają unikać błędów

ile kodu jesteś w stanie zainstalować z npm? Czy weryfikujesz jego poprawność? Czy autorki i autorzy paczek na npm nie popełniają błędów?

są naprawdę niskiego poziomu, czy ten przykład z `useInterval`, skoro nie ma to być reużywlny hook tak jak ten komponent nie jest reużywalny to można to zdecydownie prościej i czytelniej zrobić tak samo pozostałe przykłady. Za mocno subiektywny artykuł, mało merytoryczny. Na szczęście nikt nie nakazuje używania hooków, można korzystać ze starych dobrych „klas” – co jak wiemy w JS też nie jest raczej zalecane 🙂

Marcin
Marcin
7 miesięcy temu

Tak jest zalozenie ze maja byc reuzywalne ale to porownaj kod takiej reuzywalnej klasy i wtedy bedzie to mialo sens 🙂 Tezy postawione w artykule maja w sobie duzo prawdy ale sa mocno subiektywne, ale tez pewnie taki mial ten artykul byc

Marcin
Marcin
7 miesięcy temu

Co do klas to to po prostu nie sa prawdziwe klasy jak w innych jezykach 🙂 https://www.toptal.com/javascript/es6-class-chaos-keeps-js-developer-up

Kolejny argument to Abramov sam pisal ze ludzie myslą ze w tych komponentach klasowych nie ma magii a jest wiecej niz w hookach

Bartosz Kotrys
Bartosz Kotrys
7 miesięcy temu

dobre podsumowanie, mam podobne zdanie 🙂

Michał Grzegorzewski
Michał Grzegorzewski
7 miesięcy temu

React, niczym socjalizm, świetnie rozwiązuje problemy, które sam stworzył.

Podoba mi się analogia z socjalizmem 😉
…bohaterskie pokonywanie problemów nieistniejących w innych systemach!

Kuba
Kuba
6 miesięcy temu

Jak napiszesz artykuł w wersji angielskiej to prosiłbym o wrzucenie tego na stronę albo gdzieś na FB. Nie jestem tak biegły w tych tematach by się wypowiadać ale zaskoczył mnie twój wpis i z chęcią poczytałbym jak odniosą się do tego osoby kompetentne.

dzozef konrad
dzozef konrad
6 miesięcy temu

Siema, jestem javovcem, kursik ogarnalem z ciekawosci i cos tam w nastepnym projekcie ma byc z reactem. Lektura tego kursu i ewolucja kodu react’owego w kolejnych przykladach to jak podróz do jadra ciemnosci.

sdevv
sdevv
5 miesięcy temu

Mutowalność propsów w komponencie klasowym również prowadzi do bugów i w tym przypadku hooki mają bardziej naturalne zachowanie np taka metoda:

async fetchItem() {
const data = await fetch(`…/${this.props.itemId}`);

// update props.itemId

this.setState({
item: {
itemId: this.props.itemId, // nowy itemId, niezgodny z pobranymi danymi
data,
}
});
}

Michał
Michał
5 miesięcy temu

Czyli jednak wracamy do tego, co napisałem w maju 2019 😀 http://disq.us/p/21xzfl3

sdevv
sdevv
5 miesięcy temu

Zrobiłem takie oto swoje zestawienie wad i zalet na podstawie własnych doświadczeń:

Hooks
Zalety:
– ogromna modułowość i łatwość enkapsulacji. Stan, metody cyklu życia a nawet komponenty/elementy np. z podłączonym ref – pozwala to na łatwe tworzenie czystego deklaratywnego kodu, poprzez schowanie całej imperatywnej logiki
– łatwa kompozycja, jeden hook może bez problemu korzystać z wielu innych
– bardzo dobre wsparcie typowania (nie dotyczy tylko TypeScript, użytkownicy JS również na tym zyskują)
– useEffect w łatwy sposób pozwala na uniknięcie race conditions, ponieważ callback „czyszczący” jest uruchamiany przed każdym następnym uruchomieniem useEffect (w klasie jest to rozsiane po kilku metodach np. cyklu cDU)
Wady:
– wyższy próg wejścia niż klasy (wymaga zrozumienia nowego podejścia do implementacji metod cyklu życia)
– stale closures

Klasa
Zalety:
– niższy próg wejścia (metody cyklu życia są bardziej „explicit”)
– metody nie zmieniają swojej referencji pomiędzy re-renderami
Wady:
– niejawne pochodzenie wartości HOC (nie wiadomo skąd pochodzi dany prop). Co gorsza jeden HOC może nadpisać wartości innego HOCa
– jeden kontekst na klasę
– logika niepowiązana ze soba jest rozsiana po całej klasie nie ze względu na funkcje jakie wykonuje, lecz na używane przez nią metody cyklu życia
– mutowalnie zmienane propsy, które prowadzą do bugów i nieoczekiwanych rezultatów jak zmiana wartości w środku wykonywania asynchronicznej operacji