React — это самая популярная фронтенд-библиотека из экосистемы JavaScript. Она известна простотой использования и читабельностью кода, создаваемого с её применением. Это позволяет организациям самых разных масштабов успешно внедрять данную библиотеку. Но компоненты, основанные на классах, это громоздкие конструкции, с которыми непросто разобраться. Хуки позволяют работать с состоянием компонентов, с методами их жизненного цикла, с другими механизмами React без использования классов.



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

О возникновении хуков React


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

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

Компонент, основанный на классах:

import React, { Component } from 'react'
 
export default class Hello extends Component {
    render() {
        return(
            <div>
            Hello World!
            </div>
        )
    }

Этот компонент выводит в DOM элемент <div> с сообщением Hello World!.

А вот — код функционального компонента, решающего ту же задачу.

import React from 'react'
 
export default function Hello() {
    return (
        <div>
            Hello World!
        </div>
    )
}

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

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

Как хуки упрощают работу с методами жизненного цикла компонентов?


Среди методов жизненного цикла React-компонента можно отметить те, которые вызываются при его монтировании, обновлении и размонтировании.

  • Монтирование — это вставка элементов в DOM.
  • Обновление — это, как можно судить по названию, обновление элементов DOM.
  • Размонтирование — это удаление элементов из DOM.

Вот схема, на которой представлены различные методы жизненного цикла React-компонента.


Методы жизненного цикла React-компонента

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

Предположим, нам нужно загрузить данные с использованием метода жизненного цикла componentDidMount() в компоненте, основанном на классах:

import React, { Component } from 'react'
import Axios from 'axios'
 
export default class Hello extends Component {
 
    constructor(props) {
        super(props);
        this.state = { name: ""};
    }
 
    componentDidMount() {
        Axios.get('/api/user/name')
        .then(response => {
            this.setState({ name: response.data.name })
        })
    }
 
    render() {
        return (
            <div>
                My name is {this.state.name}
            </div>
        )
    }
}

А теперь решим ту же задачу в функциональном компоненте, пользуясь хуками useState и useEffect:

import React, { useEffect, useState } from 'react'
import Axios from 'axios'
 
export default function Hello() {
 
    const [Name, setName] = useState("")
 
    useEffect(() => {
        Axios.get('/api/user/name')
        .then(response => {
            setName(response.data,name)
        })
    }, [])
 
    return (
        <div>
            My name is {Name}
        </div>
    )
}

Этот код загружает нужные данные с использованием Axios API и выводит их в DOM. Хуки useEffect и useState позволяют писать более эффективный и компактный код, чем код, который пишут при использовании компонентов, основанных на классах. Такой код получается понятнее, с ним легче работать. При использовании методов жизненного цикла в компонентах, основанных на классах, приходится по-отдельности работать с методами componentDidMount(), componentDidUpdate(), componentWillUnmount(), а при использовании хуков можно просто сделать всё, что нужно, с помощью useEffect.

Хуки облегчили изучение React


Компоненты, основанные на классах, всегда были несколько громоздкими и непонятными конструкциями, особенно учитывая то, что их применение ведёт к тому, что механизмы управления состоянием компонента и многократного использования кода кажутся сложнее, чем они есть на самом деле. Это привело к тому, что многие новички избегали React, выбирая более «лёгкие» библиотеки и фреймворки. А с появлением хуков изучить React стало легче, чем прежде. Это стало одной из причин роста популярности React.


Вопросы о различных библиотеках и фреймворках на Stack Overflow

На этом графике виден постоянный рост интереса к React на Stack Overflow, продолжающийся уже много лет. Здесь же можно сравнить процент вопросов о React с процентом вопросов о других популярных JavaScript-инструментах. Этот график доказывает то, что разработчики стали чаще пользоваться библиотекой React после появления хуков.

О некоторых хуках и их предназначении


До появления хуков организация кода компонентов на основе методов жизненного цикла вынуждала разработчиков «разбрасывать» по разным компонентам код, направленный на реализацию схожей логики. Для решений этой и других мелких проблем в React были введены функциональные компоненты, что позволило упростить код и сделать его более гибким. Ещё одна задача, которую постоянно приходилось решать разработчикам с использованием не особенно удобных механизмов, заключалась в изменении состояния компонентов. Для решения этой задачи теперь используется хук useState.

▍Хук useState


Вероятно, useState — это самый распространённый React-хук. Он позволяет работать с переменными состояния в функциональных компонентах.

const loadingTuple = React.useState(true)
const loading = loadingTuple[0]
const setLoading = loadingTuple[1]
 
 
 
loading // true
setLoading(false)
loading // false

Здесь useState принимает единственный аргумент: исходное значение состояния. Он возвращает массив с переменной state и с функцией для обновления этого состояния. После этого у программиста есть всё, что нужно для работы с состоянием в функциональном компоненте.

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

▍Хук useEffect


Хук useEffect помогает программисту выполнять побочные эффекты в функциональных компонентах. То есть — вызывать функции, которые нужно выполнить после обновления DOM. Он заменяет некоторые события, позволяя вызывать функцию при изменении одной или нескольких переменных. Он принимает два аргумента: функцию и необязательный массив. Эта функция определяет то, какой именно «побочный эффект» нужно выполнить, а в массиве указывают переменные, за изменениями которых нужно наблюдать.

▍Другие хуки


  • useContext: позволяет работать с контекстом — с механизмом, используемым для организации совместного доступа к данным без необходимости передачи свойств.
  • useRef: позволяет напрямую обращаться к DOM в функциональных компонентах. Обратите внимание на то, что useRef, в отличие от setState, не вызывает повторный рендеринг компонента.
  • useReducer: хранит текущее значение состояния. Его можно сравнить с Redux.
  • useMemo: используется для возврата мемоизированного значения. Может применяться в случаях, когда нужно, чтобы функция возвратила бы кешированное значение.
  • useCallback: применяется в ситуациях, когда дочерние элементы компонента подвергаются постоянному повторному рендерингу. Он возвращает мемоизированную версию коллбэка, которая меняется лишь тогда, когда меняется одна из зависимостей.

Выше мы коснулись лишь некоторых наиболее часто используемых хуков React. Если вы хотите углубиться в их изучение — загляните сюда.

Итоги


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

Пользуетесь ли вы хуками React в своих проектах?

Комментарии (52)


  1. marshinov
    15.11.2021 16:27
    +26

    Мне одному классы больше нравились?


    1. Drag13
      15.11.2021 16:28
      +6

      Не только вам. Мне функциональные компоненты очень нравятся, но только до тех пор пока они чистые. Может я просто старый.


      1. marshinov
        15.11.2021 16:30
        +5

        Вот да. Как только неявный стейт где-то за кадром в хуках "проще" - ну хз. Может проще, пока не думаешь, как это работает под ковром.


        1. alexesDev
          15.11.2021 16:46
          +2

          Было проще, если бы react явно просил передавать currentNode из своего дома как аргумент.

          const MyComponent = (props, node) => { useState(1, node)

          currentNode есть, но спратана в кишках.


          1. marshinov
            16.11.2021 23:58

            Да, так было бы максимально понятно. Ну и всякие «нельзя в циклах, нельзя в if, надо всегда в одном порядке, чтобы мы в глобальной хешмапе (ну или что там внутри) поняли че куда сувать». Понятно, что они так две переменные сэкономили, но стоило ли? Т.е. когда в редаксе предлагали шмотки копипастить - было норм, а теперь резко боремся за каждую переменную. Ну может и хрен с ним, все-равно везде ложь: рантайм спекулирует, планировщик делает че хочет, процессор конвееризирует все и вообще из кешей вместо памяти читает. Если кругом хаки и ложки нет, то может хрен с ним с неявным стейтом.


        1. Fen1kz
          15.11.2021 17:53
          +7

          В классах это "под ковром" ещё хуже. Суть в переиспользовании логики — если в классах вы либо копипастите, либо начинаете делать HoC'и, то с хуками можете легко скомпоновать.


          Грубо говоря пока вы пишете


              useEffect(() => {
                  Axios.get('/api/user/name')
                  .then(response => {
                      setName(response.data,name)
                  })
              }, [])

          Классы реально кажутся лучше и проще, а вот когда у вас есть SomeComponent который может быть Draggable, Droppable, Transparentable и Чегоготамble, то вы городите жуткую пирамиду из НоС'ов:


          class Component {...}
          
          const MyComponent = Draggable(Droppable(Transparentable(Чегоготамble(Component))))

          Ой, погодите, почему так скучно, они же конфигурируемые?!


          class Component {...}
          
          class ЧегоготамbleComponent = Чегоготамble('some config string')(Component)
          
          const TransparentableЧегоготамbleComponent = Transparentable('50%')(ЧегоготамbleComponent)
          
          const Component4 = Draggable(true, true)(TransparentableЧегоготамbleComponent)
          
          const MyComponent = Droppable('.drop-zone')(Component4)
          

          Потом вы устаете это писать руками и ставите recompose (или что там сейчас, подскажите кто знает)


          Получается так:


          class Component {...}
          
          const MyComponent = compose(
          Draggable(true, true),
          Droppable('.drop-zone'),
          Transparentable('50%'),
          Чегоготамble('some config string'),
          )(Component)
          // Тупой вопрос для собеседований №215: В какую сторону идет композиция?

          Звучит неплохо, но теперь у вас:
          1) конфиги больше самих компонентов
          2) Очень больно передавать данные между этими НоС'ами


          Вот пример из моей игры:


          export class BaseAnimal extends React.PureComponent {
            render() {
              // Половина пропсов не актуальна без соответствующих НоС'ов
              const {classes, animal, game, children, canInteract, acceptInteraction, place} = this.props;
              // бла бла логика, рендер
            }
          }
          
          // Внешний компонент который мне нужен раз
          export const Animal = compose(
            setDisplayName('Animal')
            , setPropTypes({animal: PropTypes.instanceOf(AnimalModel).isRequired})
            , withStyles(styles)
          )(BaseAnimal);
          
          // Внешний компонент который мне нужен два
          export const InteractiveAnimal = compose(
            setDisplayName('InteractiveAnimal')
            , setPropTypes({animal: PropTypes.instanceOf(AnimalModel).isRequired})
            , connect(...)
            , InteractionTarget([
              InteractionItemType.CARD_TRAIT
              , InteractionItemType.FOOD
              , InteractionItemType.TRAIT
              , InteractionItemType.TRAIT_SHELL
              , InteractionItemType.ANIMAL_LINK
              , InteractionItemType.COVER
              , InteractionItemType.PLANT_ATTACK
            ], {
              // и тут ещё конфиг на 150 строк, не шучу
            })
          )(Animal);
          
          // Внешний компонент который мне нужен три
          export const AnimatedAnimal = AnimatedHOC(({animal}) => `Animal#${animal.id}`)(InteractiveAnimal);

          InteractionTarget и AnimatedHOC — это логика которая шарится между многими классами.


          Вот и получается, что при такой сложности классы уже не тянут то что нужно и всю эту логику можно вынести в хуки, что сделает её более прозрачной:


          const Animal  = () => {
              const classes = useStyles(); // BaseAnimal вообще не нужно
              return <div.../>
          }
          const InteractiveAnimal = () => {
             const {canInteract, acceptInteraction} = useInteractionTarget(...config);
             return <Animal .../>
          }
          const AnimatedAnimal = () => {
             useAnimation(...config);
             return <InteractiveAnimal .../>
          }



          tl;dr


          1) что классы, что хуки на простых компонентах примерно одинаковые по сложности
          2) С увеличением количества реиспользования программирование на классах смещается от самих классов к HoC'ам. Или к renderProps'ам


          А вот у них обоих хуки выигрывают с большим отрывом.


          1. marshinov
            15.11.2021 18:02
            +2

            Ну может для игр так. У меня таких страшных HoC'ов не было никогда, но ваша точка зрения понятна.


            1. Fen1kz
              15.11.2021 18:12
              +2

              Для игр, для библиотек и для хороших энтерпрайз проектов.


              Вы вот со своими классами что будете делать, если вам нужно определить stateful-поведение отдельно от сущностей? Копипастить, как обычно?




              EDIT: Отличный пример это Apollo Client


              Вот, почитайте как это было раньше: https://www.apollographql.com/docs/react/api/react/hoc/ (NSFW)


              1. strannik_k
                16.11.2021 21:17
                +3

                Ну, если не говорить про реакт и пойти, например, в сторону игр (я их когда-то разрабатывал), то многие большие игровые проекты успешно пишутся на классах. Как бы, проблема, которая недавно решена с помощью функциональных компонентов с хуками, уже лет 20-30 как решена на классах в геймдеве.

                В случае классов можно не городить кучу оберток с помощью декораторов (HOC), а выносить логику из компонента в другие классы/объекты или функции, а также воспользоваться другими паттернами композиции, например стратегией. Если интересно, можете почитать о различных подходах в моей статье: https://habr.com/ru/post/545368/


                1. Fen1kz
                  17.11.2021 13:35

                  Хорошая статья, спасибо.


                  Правда в случае этой статьи, мы все-таки говорим про реакт и я доказывал именно преимущество хуков над классами в реакте, а не вообще по жизни


                  1. strannik_k
                    17.11.2021 18:47

                    Ок, мне было не понятно. Хотел сказать, что проблема не в самих классах, а в реализации компонентов на классах. Можно было сделать лучше.


            1. faiwer
              15.11.2021 18:28
              +1

              Мне кажется в любых больших проектах так. Не только в играх. Эти HoC иногда собираются в такие безумные пирамидки… К примеру у нас на проекте не compose из redux а самописный метод pipe. А так как TypeScript то он ещё и типизирован:


              под спойлером TS печаль
              /* prettier-ignore */
              declare function pipe<A, B, C>
                (
                  ab: (arg: A) => B,
                  bc: (b: B) => C
                ): (arg: A) => C;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D>
                (
                  ab: (...args: A) => B,
                  bc: (b: B) => C,
                  cd: (c: C) => D
                ): (...args: A) => D;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E>
                (
                  ab: (...args: A) => B,
                  bc: (b: B) => C,
                  cd: (c: C) => D,
                  de: (d: D) => E,
                ): (...args: A) => E;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E, F>
                (
                  ab: (...args: A) => B,
                  bc: (b: B) => C,
                  cd: (c: C) => D,
                  de: (d: D) => E,
                  ef: (e: E) => F,
                ): (...args: A) => F;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E, F, G>
                (
                  ab: (...args: A) => B,
                  bc: (b: B) => C,
                  cd: (c: C) => D,
                  de: (d: D) => E,
                  ef: (e: E) => F,
                  fg: (f: F) => G
                ): (...args: A) => G;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E, F, G, H>
              (
                ab: (...args: A) => B,
                bc: (b: B) => C,
                cd: (c: C) => D,
                de: (d: D) => E,
                ef: (e: E) => F,
                fg: (f: F) => G,
                gh: (g: G) => H
              ): (...args: A) => H;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E, F, G, H, K>
              (
                ab: (...args: A) => B,
                bc: (b: B) => C,
                cd: (c: C) => D,
                de: (d: D) => E,
                ef: (e: E) => F,
                fg: (f: F) => G,
                gh: (g: G) => H,
                hk: (h: H) => K,
              ): (...args: A) => K;
              /* prettier-ignore */
              declare function pipe<A extends unknown[], B, C, D, E, F, G, H, K, L>
              (
                ab: (...args: A) => B,
                bc: (b: B) => C,
                cd: (c: C) => D,
                de: (d: D) => E,
                ef: (e: E) => F,
                fg: (f: F) => G,
                gh: (g: G) => H,
                hk: (h: H) => K,
                kl: (h: K) => L,
              ): (...args: A) => K;

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


              1. marshinov
                15.11.2021 18:33

                Ну redux сам по себе копипасту создает:)) Причем в ng эта проблема просто не существует из-за наличия сервисов.


                1. faiwer
                  15.11.2021 18:36
                  +1

                  А redux тут вообще не причём. Наши HoC с ним не связаны. Чаще всего это всякие context.Provider-ы разных мастей.


                  P.S. а причём тут Angular?


                  1. marshinov
                    15.11.2021 19:05
                    +2

                    Просто сравниваю. У ng тоже много родовых травм (например, html-шаблоны), но там хорошо сделана структура приложения: feature modules, services, components и везде четко написано, что куда нужно совать, а что не нужно. И "It’s hard to reuse stateful logic between components" просто не существует в ng: делаешь сервис и ничего не hard to reuse. И главное эта логика очень простая и понятная для любого программиста с опытом в enterprise. Хуки вводят новый концепт, который надо учить. Для меня это выглядит как сущность, которую наплодили с понятной надобностью конечно... Но что мешашло IOC или подобие partial application прикрутить, чтобы внедрять зависимости? Типа с хуками проще? Ну может.


                    1. Fen1kz
                      15.11.2021 19:15

                      Уже не очень помню ангуляр 1, но там, кажется, нельзя делать так, чтобы сервис был на каждый инстанс компонента. Во втором это норма?


                      1. marshinov
                        15.11.2021 19:21

                        1 и 2+ - это вообще 2 разных фреймворка, там почти все переделали.


                      1. essome
                        15.11.2021 21:55

                        Во втором можно делать как угодно, но все же сервис на каждый инстанс компонента нужен очень редко.


                    1. Drag13
                      15.11.2021 19:15
                      +2

                      И, кстати, в Angular есть директивы, они могут как раз навешивать кастомную логику на элемент, тот же draggable.


                    1. LEXA_JA
                      15.11.2021 19:30
                      +1

                      Хуки это в первую очередь способ сделать независмый, переиспользуемый кусок логики привязанный к жизненному циклу компонента. Это замена для render props и HoC's (и ранне миксинов) в первую очередь. Со старыми методами была куча проблем, связанная с тем, что все общались через общие пропсы, конфликты имен и т.д. Просто хуки используются и как замена локальному стейту компонента, как в классовых компонентах.
                      Для IoC используется React Context. Вообще многие библиотеки выглядят в таком виде:

                      const client = new Client();

                      <ClientProvider.Provider client={client}>

                      {children}
                      </ClientProvider.Provider>
                      //
                      const client = useClient()


                      Есть класс отвечающий за логику. Он закидывается в компоненты через контекст. И есть хук который отвечает за привязку к жизненному циклу компонента.
                      Мне кажется в таком виде похоже на сервисы.


                      1. marshinov
                        16.11.2021 23:42

                        Вы предлагаете смотреть на хуки как на короткие версии render props/hoc/миксины, лишенные проблем конфликта имён в props, а неявный useState и useEffect (жизненный цикл) воспринимать как аспект реализации и не горевать, что их за кулисы убрали? + как бонус компоненты видны без обёрток в консольке. Правильно понял?


              1. essome
                15.11.2021 22:16
                +1

                А зачем это? За 5 лет использования ts в angular не видел ничего подобного, что этот код делает?


                1. faiwer
                  15.11.2021 22:32

                  В Angular-е ООП, а в ООП не требуется композиция функций, поэтому и не сталкивались. Вам по сути нечего композировать, т.к. большая часть ваших функций это методы классов. А когда у вас половина проекта это тысячи небольших функций, которые вы с друг другом по разному стыкуете, тогда и начинаются все эти примитивы и пайп-операторы.


                  Формально можно и без них, но читаемость страдает. Скажем:


                  // 1. naive
                  d(c(b(a(source))); // a => b => c => d =>
                  // 2. pipe
                  pipe(a, b, c, d)(source);
                  // 3. pipe operator
                  source |> a |> b |> c |> d;

                  В React компоненты это, чаще всего, просто функции. И чтобы добавить к ним какую-нибудь типовую функциональность часто используют HoC-и (хоки). Это функции-врапперы, которые принимают на входе один компонент, а возвращают обёртку над ним, которая внутри вызывает тот самый компонент, но теперь с перламутровыми пуговицами. И в клинических случаях такие матрёшки могут состоять из 10 уровней.


                  1. marshinov
                    16.11.2021 11:23
                    +1

                    Есть ощущение, что это горе от ума и слишком мелко нарезанные компоненты просто


                    1. faiwer
                      16.11.2021 11:43
                      +1

                      Нет. Ваши ощущения вас обманывают


                      • Во-первых HoC-и это экстра-функциональность поверх какого-нибудь компонента, и тут вообще не важно, насколько он там раздроблен уровнем ниже. Глубина матрёшек из HoC-ов не изменится.
                      • Во-вторых чем ближе вы к ФП, тем больше у вас подобных штук. Думаю про монады, функторы, каррирование и прочий зоопарк вы слышали. Да, в React они не нужны, но вот в хардкорном ФП без них вы будете как без рук и ног. Это ведь кирпичики на которых строятся сложные приложения, построенные на композиции функций. А ФП это целые оркестры\лабиринты из функций. Такая парадигма. И монады там не потому что они всё слишком мелко нарезают. И, пожалуйста, не надо думать что они там все глупые и у них "горе от ума". Это просто другой мир со своими правилами.
                      • В третьих тут дело не только в React. Такие штуки нужны и в повседневном коде, когда вам приходится иметь дело с большим количеством мелких функций (а многие из нас обожают иметь дело именно с ними).

                      Я повторю, то что вы их не используете, это не потому, что у вас архитектура правильнее (или неправильнее), а потому что в Angular ООП. А в ООП почти все функции в проекте завязаны на state своего instance-а класса. Т.е. вы в любом случае их не за pipe-ете, т.к. без this state-а ваши методы не работают. Вам просто нечего pipe-ать. У вас своя собственная вселенная с ООП-паттернами, декораторами, IoC контейнерами и пр… Можно было бы тоже сказать, что это всё "горе от ума", но нет, это просто свои инструменты, каждому из которых есть определённое назначение.


                      P.S. в React пожалуй нет никакой границы вроде "тут я слишком мелко нарезал". Даже однострочные компоненты — ОК. Например такие:


                      export const Separator = () => <hr className={css.separator}/>;

                      Это не имеет никаких больших penalty по производительности и отлично сказывается на качестве кода. Особенно когда таким образом достигается разделение business-логики и презентационной. Например в UI библиотеках у любого маломальски сложного компонента очень много допустимых props. И условная кнопка может быть:


                      <UI.Button
                        iconAlign="left"
                        theme="warning"
                        dir="right"
                        text="submit"
                        type="submit"
                        disabled={disabled}
                        className={css.submitButton}
                        blabla1...N={...}
                      />

                      Удобно когда вся эта мишура вынесена куда-нибудь с глаз долой и в реальной форме вы видите код вида:


                      export const SubmitButton = ({ disabled }: { disabled: boolean }) 
                        => <Ui.Button .../>;
                      ...
                      import { SubmitButton } from './Controls';
                      ...
                      <Form ...>
                         ...
                        <SubmitButton disabled={formIsValid}/>
                      </Form>

                      В React нет никаких проблем с тем чтобы раздробить ваши компоненты до любого уровня. Хоть каждый тег компонентом обернуть (если на то есть объективная причина).


                      1. markelov69
                        16.11.2021 12:17
                        +1

                        И, пожалуйста, не надо думать что они там все глупые и у них «горе от ума». Это просто другой мир со своими правилами.

                        они там все глупые и у них «горе от ума»

                        Ведь так оно и есть на самом деле.
                        Это просто другой мир со своими правилами.

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


                      1. marshinov
                        16.11.2021 12:33

                        Монады и функторы к хукам не имеют никакого отношения, если только не считать, что хуки - это a la state monad in disguise. Я же не говорю, что есть проблемы с дроблением. Я говорю, что, наоборот, не надо так мелко. Хуки задачу свою вроде как решают, просто «своим особым способом». В итоге есть накопленный опыт в других технологиях и есть особый react way, который как-бы функциональный, но на самом деле не совсем.


                      1. faiwer
                        16.11.2021 12:36

                        • Монады я привёл в пример не относительно хуков, а относительно направления мысли. Вектор.
                        • Мы тут не про хуки говорили, а про хоки (хукам не нужен pipe)
                        • Да у React есть свой react way. Всё так. Стоит правда отметить что свой way в UI фреймворках есть у всех. Просто у React он особенно странный.
                        • Мелконарезанные компоненты не имеют отношения ни к хукам, ни к хокам. Мелконарезанные компоненты это очень хорошо.


                      1. markelov69
                        16.11.2021 13:26
                        +1

                        Мелконарезанные компоненты это очень хорошо.

                        Нет. Очень тонкая грань, все индивидуально, касаемо каждого конкретного компонента и случая.


                      1. faiwer
                        16.11.2021 13:31

                        Я выше привёл два примера (<Separator/> и <SubmitButton/>). Я нарушил там эту "грань"? Если да, то почему? Если нет, то можно пример где "грань" нарушена? Ну т.е. что такого нужно сделать чтобы было слишком "мелко нарезано"? И почему?


                        Я понимаю почему:


                        const isItemActive = (item: Item): boolean => 
                          item.isActive

                        Это перебор, в большинстве случаев. Но подобного со стороны React не встречал.


                1. amakhrov
                  16.11.2021 20:42
                  +2

                  Если вы конкретно про перегруженную функцию pipe - то в angular вы наверняка используете Observable#pipe по сто раз на дню. Загляните под капот - увидите ту же самую простыню из перегруженных сигнатур: https://github.com/ReactiveX/rxjs/blob/master/src/internal/Observable.ts#L349


              1. Drag13
                15.11.2021 22:32
                +1

                У меня вот такое мини

                export function pipe<I, R1, R>(f1: (a: I) => R1, f2: (a: R1) => R): (a: I) => R;
                export function pipe<I, I1, R1, R>(f1: (a: I, b: I1) => R1, f2: (a: R1) => R): (a: I, b: I1) => R;
                export function pipe<I, R1, R2, R>(
                    f1: (a: I) => R1,
                    f2: (a: R1) => R2,
                    f3: (a: R2) => R
                ): (a: I) => R;
                export function pipe<I, I1, R1, R2, R>(
                    f1: (a: I, b?: I1) => R1,
                    f2: (a: R1) => R2,
                    f3: (a: R2) => R
                ): (a: I, b?: I1) => R;
                export function pipe<I, R>(): (a: I) => R {
                    const args = Array.from(arguments);
                    return function executer(...a) {
                        return args.reduce(
                            (prevResult, f) => (Array.isArray(prevResult) ? f(...prevResult) : f(prevResult)),
                            a
                        );
                    };
                }


          1. Fen1kz
            15.11.2021 18:07
            +6

            Хоть сам статью пиши, потому что эта убогая что капец. Хуки у нас появились оттого что лайвсайкла нет в функциональных компонентах бть, ага.


            Документация реакта, ПЕРВЫЙ пункт в мотивации:



            Хоть бы один пункт из неё взяли >..<


          1. wheercool
            16.11.2021 00:26
            +2

            Заворачивайте в декораторы и не парьтесь


          1. essome
            16.11.2021 11:44
            +2

            Вы в примере с классами вписали конфиги, а с хуками нет, что-бы код был короче)

            Классы реально кажутся лучше и проще, а вот когда у вас есть SomeComponent который может быть Draggable, Droppable, Transparentable и Чегоготамble, то вы городите жуткую пирамиду из НоС'ов:

            В angular на эти все вещи создается по директиве, которая независима от класса и может работать с любым (или по желанию определенным) тегом/компонентом.

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

            <my-component draggable></my-component>

            Директив при этом можно навесить сколько угодно.

            Может просто в react не умеют в классы?


            1. faiwer
              16.11.2021 11:53

              Может просто в react не умеют в классы?

              React определённо не умеет в классы. Даже не пытаются. Они уже почти deprecated.


      1. essome
        15.11.2021 17:42
        +3

        Можно, но по факту нельзя, так как хуки стандарт в реакте сейчас.


        1. marshinov
          15.11.2021 17:45
          +10

          Плюс это все пиарят как что-то хорошее и "простое". "Классы сложно, поэтому давайте вот на хуках". Але, если вам классы сложно, то какие нафиг "функциональные компоненты"?


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


    1. Mox
      15.11.2021 21:11
      +4

      Я сначала в штыки воспринял хуки, и в старых проектах заблокировал их внедрение ради консистентности подхода

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

      Что-то вроде

      const { handleSubmit, handleListItemToggle, listItems } = useMyCustomScreenHook();

      В итоге

      • Функциональные компоненты получились гораздо чище классов, ведь из своего хука я экспортирую только нужные методы, абстрагируясь от организации state и того как именно это работает в React.

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

      • Некоторые хуки можно сделать системными. Например - в одном проекте понадобилось пулить несколько сторонних REST API для получения статусов, и был написал хук для этого, который оч красиво встал во все места где это нужно

      P.S. Пишу про экраны, потому что сейчас делаю проекты на React Native.


  1. wheercool
    16.11.2021 00:32
    +10

    Все смешалось люди, кони.

    Особенно понравилась логическая цепочка: появление хуков сделало реакт более понятным, поэтому кол-во вопросов на SO возросло )


  1. Alexandroppolus
    16.11.2021 09:05
    +1

    Старый добрый реактовский Context API был ужасен. useContext - одно из самых крутых улучшений. Но не единственное. Как уже говорилось, проще декомпозировать логику (которую можно реализовывать на классах и хранить экземпляры в рефах).


  1. exewebru
    16.11.2021 11:13
    +4

    Дважды прочитал статью, но так и не понял чем хуки лучше классов.


  1. Elvizmarco
    16.11.2021 14:52

    У меня вот вопрос, уже сто раз видел такие примеры. Хоть кто-то пишет код запроса в компонентах? А не создается какая то оберточка для екшена?


    1. amakhrov
      16.11.2021 20:52
      +2

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

      Кроме того, вполне себе есть паттерн без thunks - компонент реализует асинхронную логику, и уже диспатчит в стор готовые результаты. (опять же для случая, когда эту логику переиспользовать нигде не требуется)


  1. dedotmoroz
    16.11.2021 14:52
    +1

    Мне в хуках не хватает второго аргумента в setState - setState(updater, [callback])

    Приходится колхозить.


    1. exewebru
      16.11.2021 21:05
      +1

      Ещё нет forceUpdate


      1. faiwer
        16.11.2021 21:36

        export const useForceUpdate = () => {
          const [, setState] = useState({});
          return useCallback(() => { setState({}); }, []);
        }

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


        1. exewebru
          17.11.2021 09:07
          +1

          Какой смысл в библиотеке, если банальные вещи попросту не реализованы? Хуки выглядят как полуфабрикат.


  1. ivgrin
    16.11.2021 14:52
    +1

    Этот график доказывает то, что разработчики стали чаще пользоваться библиотекой React после появления хуков.

    График также "доказывает" увеличение популярности библиотеки с момента появления COVID-19.


  1. Alinaki
    17.11.2021 01:46
    +2

    Ужасно пустая статья. Ну и useRef Отношения к DOM не имеет.