Pull to refresh

Comments 29

UFO just landed and posted this here
Что увидел? Всё правильно написал сначала, надо делать clearInterval, ни в классовом компоненте ни в функциональном этого нет.
Хуки — это новая функция, добавленная в React v16.8. Хуки позволяют использовать все возможности React без написания классовых компонентов.

Хоть технически хуки и являются функциями (классы в жс тоже технически являются функциями), хуки это скорее целая парадигма обработки сайд эффектов, основанная на эффектах в ОКамл, позволяющая сохранять значения при многократном вызове функции, оставляя функцию при этом чистой.


И нет, не все можно сделать на хуках, на хуках пока что нельзя ловить ошибки, например (componentDidCatch).


Другая причина в том, что не существует конкретного способа повторно использовать логику компонента, наделенного состоянием. Несмотря на то, что HOC (Higher-Order Component) и шаблоны Render Props (метод передачи props от родителя ребенку, используя функцию или замыкание) решают эту проблему, здесь требуется изменить код классового компонента. Хуки позволяют совместно использовать логику с отслеживанием состояния без изменения иерархии компонентов.

Ничего они не требуют изменять и рендер пропсы никуда не пропали. Основная проблема с HOC в том, что обычно навешивается по три-четыре, а то и больше HOC и, мало того, что они могут передавать конфликтующие значения, порой становится сложно понять кто и по какой причине передал значение, особенно если оно неправильное. Ну и их сложнее тестировать, чем хуки.


Ну и контекст до хуков был труден в использовании, мягко говоря.


Классовые компоненты не очень хорошо минимизируются, а также делают горячую перезагрузку ненадежной. Это еще одна причина для создания хуков.

У нас один из самописных хуков (точнее целое семейство) вообще отломали горячую перезагрузку, пункт не засчитан. И не то чтобы они были какими-то особо хитрыми, так, обертка вокруг редуксовского диспатча.


Ну и вообще, если разглагольствовать, то основная причина, конечно же, стремление построения реактивной библиотеки с чистыми функциями. Мало того, что функциональные компоненты обычно меньше и проще чем классовые, так они еще и не содержат обычно стейтов и сайд эффектов, так что их было просто тестировать и вообще они полюбились людям. Но команда реакта достаточно долго думала как же все таки дать возможность делать lifecycle хуки в функциональных компонентах, вот и придумала. Правда это принесло в них состояние, и хотя функции сами по себе по-прежнему чистые, компоненты опять могут хранить внутренний стейт, что опять повышает требования к тестированию.


Будут ли хуки React работать внутри классовых компонентов — Нет.

Ну если очень хочется, то да, да и никто не мешает миксовать компоненты или использовать хуки в HOC.


Можно обновить значение переменной состояния, просто передав новое значение в функцию обновления или передав callback. Второй способ с передачей callback-функции безопасен в использовании.

Без контекста не совсем понятно что имеется ввиду под "безопасен". Я бы обязательно спросил что это значит и ожидал услышать про захват замыканий и использование устаревшего значения при, например, асинхронной логике в обработчике (заодно, как показывает практика, это не самый тривиальный случай, который многие не понимают), или нежелание тянуть доп. параметр и пересоздавать каждый раз кэлбэк когда поменялось значение в сторе.


Теперь к вопросам. Большинство из них — перепечатка из документации. Но довольно важные моменты остались за кулисами. Например, нет ни одного вопроса про useCallback. А именно useCallback — та невеселая штука, которая превращает красивые примеры в не очень то красивый реальный код. Ну т.е. useState2 вообще должен выглядеть примерно так:


import React, { useState } from "react";

function Counter() {
    const [count, setCount] = useState(0);

    const incrementCount = React.useCallback(
    () => {
        setCount((prevCount) => {
            return prevCount + 1;
        });
    },
   [setCount]);

    const decrementCount = useCallback(
    () => {
        setCount((prevCount) => {
            return prevCount - 1;
        });
    }, 
    [setCount]);

    return (
        <div>
            <strong>Count: {count}</strong>
            <button onClick={incrementCount}>Increment</button>
            <button onClick={decrementCount}>Decrement</button>
        </div>
    );
}

Еще не вижу правил использования хуков (никаких хуков внутри условных выражений, нельзя делать массивы хуков и тому подобное), заодно мне лично всегда интересно спрашивать "почему такие правила вообще существуют", но это уровень чуть выше.


Спасибо, мы вам перезвоним.

В последнем примере useCallback как раз не нужен — не дает никакого бонуса.

нельзя делать массивы хуков
Впервые про это слышу. И проблем не вижу. Где говорится, что нельзя делать массивы хуков?

Это косвенно следует из «нельзя использовать хуки в циклах» https://reactjs.org/docs/hooks-rules.html


Конечно, теоретически ничего не взорвется, но нужно быть супер уверенным в том что массив не может измениться никогда.

Массив хуков я понял так:
const hooksResults = [useState(0), useState(1), useState(2)];
Я не вижу варианта, как здесь без условий и циклов поломать работу хуков.
Массив хуков я понял так:

const handlers = collection.map(item => 
  useCallback(() => onClick(item), [item, onClick])
);

Полагаю речь о чём-то подобном. В вашем примере массив константен. ^ а тут нет.

Не, ну это ближе к циклам, чем к массивам)
Если в циклах не рекомендуется использовать, то понятно, что в перебирающих методах тоже) Не говоря уже о том, что во вложенных функциях тоже не рекомендуется.
то понятно, что в перебирающих методах тоже

Это вам понятно. А многим будет не очевидно. Достаточно много народу рассматривает методы массивов не как обёртки над for-циклом, а как магию, которую "ну V8 умные же люди пишут, они там всё точно оптимизировали на O(1)" :-)

По поводу useCallback в последнем примере — это лишнее. В таком "конечном" компоненте, который рендерит исключительно примитивы, useCallback это лишние строчки и лишняя работа. Быстрее работать этот компонент не станет. Если эти функции не передаются дальше, как часто бывает в кастомных хуках, то от сохранения identity нет никакой пользы.

На самом деле это каждый раз спорный пример. Да заодно мы, например, не используем нативные кнопки, а используем обертки. И никто не может сказать что оптимальнее — пересоздать функцию и перерисовать обертку или useCallback.


Покуда каждый раз думать и выбирать что лучше — лишняя когнитивная нагрузка, зачастую рекомендация оборачивать все, если вы не делаете непосредственно конечный элемент (атом или саму обертку над кнопкой).


Про конкретный пример я согласен, тут он не нужен, но такое проклятие примитивных примеров — они слабо отражают реальный код и нужно натягивать сову на глобус.

Да, уже не раз обсуждали в комментах — на хуках в целом писать сложнее, они подразумевают множество условностей, легче пишется непроизводительный код, разрастаются render-функции, взаимосвязи между хуками превращаются в клубок. В концепции разрабов Реакта как бы все логично по поводу зачем хуки, как и логично, зачем FLUX и другой react-way, но в реальности это оказывается очень неудобно использовать.


Я вот после трех крупных проектов на хуках (где в каком-нибудь кастомном wysiwyg было 30-40 кастомных хуков в рендере и они использовали результат работы друг друга) вернулся к классам и забыл про все эти беды. Жизненный цикл — человекопонятный, стрелочные методы — равны по ссылкам при ререндерах, render-функция — чистая, все организовано в семантичные геттеры и методы, бонус — сокращение времени на создание командных гайдов, ревью, обсуждения.


Касательно "недостатков", описанных в статье — this совсем не сложный концепт для понимания, а базовый; увеличенный размер классовых компонентов — ну в сжатом виде на 100 компонентов добавится 1кб, даже упоминания не стоит; горячая перезагрузка — проблема любых сложных приложений, для надежности лучше пользоваться полной перезагрузкой; замена условностями (типа возврата анонимной функции в useEffect) человекопонятного жизненного цикла и явного порядка выполнения операций — скорее негативная оптимизация, из разряда сокращения названий переменных до одного символа. Заведение 4 слушателей в componentDidMount и их последовательная очистка в componentWillUnmount или они же, сгруппированные по 4 хукам — скорее дело вкуса, чем явного превосходства одного из вариантов.


Остается только более простое переиспользование кастомных хуков — из классов можно вынести в общую папку реализацию метода, но не логику его выполнения в жизненном цикле. Это решается через HOC, хотя и не элегантно.

А мы наоборот вздохнули полной грудью перейдя на хуки. Да там очень много мелочей, сложных для восприятия. Но возможности описывать сложные взаимодействия отдельными кусочками кода настолько сильно, в нашем случае, превалирует, что классы вспоминаем как страшный сон. Решать проблемы хуков оказалось много проще, чем решать проблемы классов. Это я вам говорю после написания десятка 3-х этажных хуков очень большой сложности. Сильно проще это всё дебажить чем в плоской классовой структуре.


Но наверное правдивый ответ на такой вопрос один — это две разные парадигмы, и какая вам привычнее, с той у вас лучше и будет получаться.

Зависит не только от привычности, но и от окружающих view-слой библиотек и паттернов, от опыта команды и специфики проекта. Да, я бы не стал прямо гвоздями прибивать — классы и все тут, так как сам писал эти многотомные хуки и понимаю лаконичность функционального подхода. На чистых контекстах, локальных стейтах и иммутабельности сам бы без вариантов выбрал хуки, на mobx — классы без вариантов, так что отдельно от экосистемы неправильно было бы рассматривать.

Соглашусь. Без immutability хуки скорее боль.

на mobx — классы без вариантов

Зачем? Вся логика компонента переезжает в мобиксовые сторы, а сами компоненты удобнее сделать функциями, которые с помощью хуков получают тот или иной стор.

Да заодно мы, например, не используем нативные кнопки, а используем обертки

Дьявол кроется в деталях. Чем тяжелее ваши обёртки — тем актуальнее мемоизация. Но в примере выше просто теги.


выбирать что лучше — лишняя когнитивная нагрузка

С одной стороны это аргумент. Но его начисто кладёт на лопатки контр-аргумент:


  • код с useCallback гораздо хуже читается.

Т.е. когнитивная нагрузка от выбора и рядом не стояла с повышенной когнитивной нагрузкой при чтении и рефакторинге такого кода.

Вообще, если говорить про текущее состояние (мы от него немного постепенно отходим, но пока оно работало неплохо), мы балансируем с useReducer и контекстами для локальных стейтов компонента, для глобального стейта используем редакс (преимущественно для того, что взаимодействует с сервером), для форм — формик. Для редакса написали свой хук — useSaga. Как результат — useCallback большая редкость, в основном, в составе переиспользуемых хуков. useMemo я тоже стараюсь избегать в пользу реселекта из редакса. Практика показывает, что один большой, специфичный под компонент, реселект и один useSelector не только в разы проще тестировать, но и лучше для производительности.


P.S. useSaga принимает в качестве аргумента сагу, возвращает Result (который может быть init, loading and done) и функцию для запуска саги. Внутри он диспатчит экшен и ждет ответный экшен об выполнении саги делает магию с каналами и форками внутри саги. Я знаю что это злой хук, который противоречит идеям и концепциям и редакса, и реакта. Но работает и просто умопомрачительно сокращает код.

useMemo я тоже стараюсь избегать в пользу реселекта из редакса.

Они же разные… Селектор ведь нужно создать заранее и у него кеш с 1-им значением. Т.е. его нельзя без бубнов применять к переиспользуемым компонентам. Поэтому там идут костыли с mstp и фабриками. И такие же с mdtp.


Практика показывает, что один большой, специфичный под компонент, реселект и один useSelector не только в разы проще тестировать, но и лучше для производительности.

Проще, чем что? Мы на проектах выжигаем калёным железом redux, а хуки тестируем специальной либой (на память не скажу какой) — и там всё удобно тестируется, включая эффекты. Плюс моки.


Да и тот код что с redux я бы не стал противопоставлять тому, что с хуками, т.к. самая мутная и сложная часть приходится на UI, и, следовательно не имеет отношения к redux. А mstp обычно простые как валенки — взять Х, Y, Z, сформировать из них Ö, и вернуть как prop. Обычно это даже тестировать не нужно (зачем вы их тестируете, кстати?). Хотя бывают, конечно, случаи, когда за селектором стоит 500 строк сложного кода (в 20 методах), которые запускают адронный колллайдер :D Но это редкость.


В целом я не очень понял, про что был ваш комментарий и как он связан с предыдущими :)

у т.е. useState2 вообще должен выглядеть примерно так:

Не должен. У вас накладные расходы на useCallback сопоставимы, если не превышают, с расходами на removeEventListener + addEventListener


Правда это принесло в них состояние, и хотя функции сами по себе по-прежнему чистые

Тут стоит уточнить, что компонент, который задействует хотя бы 1 хук — по определению impure. Ибо все хуки — side effects. А значит и component — с side-effects. В лучшем случае вам удастся сохранить некоторые кусочки вашего компонента чистыми (например callback в useMemo). Это в принципе неплохо.


(заодно, как показывает практика, это не самый тривиальный случай, который многие не понимают

С козырей зашли :) Откровенного говоря, большая часть разработчиков знает хуки настолько поверхностно, что этот вопрос поставит их в тупик. На позицию архитектора, имхо, можно смело задавать вопрос вида: как бы вы реализовали поддержку хуков? Вменяемый ответ на этот вопрос выявит наличие или отсутствие понимания, что же стоит за хуками.

Тут стоит уточнить, что компонент, который задействует хотя бы 1 хук — по определению impure. Ибо все хуки — side effects. А значит и component — с side-effects. В лучшем случае вам удастся сохранить некоторые кусочки вашего компонента чистыми (например callback в useMemo). Это в принципе неплохо.

Это как посмотреть. Если копать в теорию — функции остаются чистыми, а хуки — эдакий способ передачи аргументов внутрь функции. Т.е. в отличие от класса, который хранит значения внутри себя, хуки (эффекты в окамл) все таки запрашиют значения у того, кто их запускает. Т.е. вы можете перевызвать функцию сколько угодно раз, передав значения эффектов как вам нужно, разве что синтаксис отличается от передачи аргументов. Что-то вроде


// псевдоязык
run:
  Component(a, b, c);
effects:
  foo: 1,
  bar: 2,
  baz: 3,

Так что в теории, функция полностью зависит от переданных аргументов и все еще чистая.


На практике вы, безусовно, правы, и никто так компоненты тестировать не будет, так что функции нечистые. Но это вопрос к организации стейта: вообще можно добиться хороших результатов по чистоте функции с применением useReduce и контекстов, ну или вообще того же редакса.

а хуки — эдакий способ передачи аргументов внутрь функции

Ну это уже читы пошли :) Так можно далеко нарассуждать и белое станет чёрным, а чёрное белым.

Навскидку:
1) Для чего нужен useCallback. Вопрос хорош тем, что можно развернуть тему оптимизации и мемоизации в Реакте.
2) Попросить рассказать, как примерно устроены хуки изнутри. Если пациент не знает (не читал), то неплохой повод порассуждать.

Никакого примера с useRef, useMemo, и, как выше упомянули, useCallback. Неплохо было бы попросить написать кастомный хук для какой-то задачи.
Согласен, самое интересное — это скорей useMemo, useRef и useCallback
понравилось изложение и примеры, обьяснено просто и доступ, спасибо!
Спасибо за статью. Небольшой рефакторинг примера с useState:

import { useState } from 'react'

function Profile() {
  const [profile, setProfile] = useState({
    name: 'John',
    age: 30
  })

  function handleChange({ target: { value, name } }) {
    setProfile({ ...profile, [name]: value })
  }

  const { name, age } = profile
  return (
    <div>
      <form>
        <input type='text' value={name} onChange={handleChange} name='name' />
        <input type='text' value={age} onChange={handleChange} name='age' />
        <h2>
          Name: {name}, Age: {age}
        </h2>
      </form>
    </div>
  )
}
Вижу понимать как работает useState изнутри никому не надо. Дали функцию, вызывайте, а как она маппит состояние на компонент и где его хранит никому не интересно. В принципе правильно, если юзание this это уже большой челендж :)
Sign up to leave a comment.

Articles