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


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


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


Хуки делают переиспользование кода удобнее


Давайте представим компонент, который рендерит простую форму. Что-то, что просто выведет несколько инпутов и позволит нам их редактировать.


Примерно так, если сильно упростить, этот компонент выглядел бы в виде класса:


class Form extends React.Component {
    state = {
        // Значения полей
        fields: {},
    };
    render() {
        return (
            <form>
                {/* Рендер инпутов формы */}
            </form>
        );
    };
}

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


class Form extends React.Component {
    constructor(props) {
        super(props);
        this.saveToDraft = debounce(500, this.saveToDraft);
    };
    state = {
        // Значения полей
        fields: {},
        // Данные, которые нам нужны для сохранения черновика
        draft: {
            isSaving: false,
            lastSaved: null,
        },
    };
    saveToDraft = (data) => {
        if (this.state.isSaving) {
            return;
        }
        this.setState({
            isSaving: true,
        });
        makeSomeAPICall().then(() => {
            this.setState({
                isSaving: false,
                lastSaved: new Date(),
            }) 
        });
    }
    componentDidUpdate(prevProps, prevState) {
        if (!shallowEqual(prevState.fields, this.state.fields)) {
            this.saveToDraft(this.state.fields);
        }
    }
    render() {
        return (
            <form>
                {/* Рендер информации о том, когда был сохранен черновик */}
                {/* Рендер инпутов формы */}
            </form>
        );
    };
}

Тот же пример, но с хуками:


const Form = () => {
    // Стейт для значений формы
    const [fields, setFields] = useState({});
    const [draftIsSaving, setDraftIsSaving] = useState(false);
    const [draftLastSaved, setDraftLastSaved] = useState(false);

    useEffect(() => {
        const id = setTimeout(() => {
            if (draftIsSaving) {
                return;
            }
            setDraftIsSaving(true);
            makeSomeAPICall().then(() => {
                setDraftIsSaving(false);
                setDraftLastSaved(new Date());
            });
        }, 500);
        return () => clearTimeout(id);
    }, [fields]);

    return (
        <form>
            {/* Рендер информации о том, когда был сохранен черновик */}
            {/* Рендер инпутов формы */}
        </form>
    );
}

Как мы видим, разница пока не очень большая. Мы поменяли стейт на хук useState и вызываем сохранение в черновик не в componentDidUpdate, а после рендера компонента с помощью хука useEffect.


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


// Хук useDraft вполне можно вынести в отдельный файл
const useDraft = (fields) => {
    const [draftIsSaving, setDraftIsSaving] = useState(false);
    const [draftLastSaved, setDraftLastSaved] = useState(false);

    useEffect(() => {
        const id = setTimeout(() => {
            if (draftIsSaving) {
                return;
            }
            setDraftIsSaving(true);
            makeSomeAPICall().then(() => {
                setDraftIsSaving(false);
                setDraftLastSaved(new Date());
            });
        }, 500);
        return () => clearTimeout(id);
    }, [fields]);

    return [draftIsSaving, draftLastSaved];
}

const Form = () => {
    // Стейт для значений формы
    const [fields, setFields] = useState({});
    const [draftIsSaving, draftLastSaved] = useDraft(fields);

    return (
        <form>
            {/* Рендер информации о том, когда был сохранен черновик */}
            {/* Рендер инпутов формы */}
        </form>
    );
}

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


Хуки позволяют писать более интуитивно-понятный код


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


class ChatApp extends React.Component {
    state = {
        currentChat: null,
    };
    handleSubmit = (messageData) => {
        makeSomeAPICall(SEND_URL, messageData)
            .then(() => {
                alert(`Сообщение в чат ${this.state.currentChat} отправлено`);
            });
    };
    render() {
        return (
            <Fragment>
                <ChatsList changeChat={currentChat => {
                        this.setState({ currentChat });
                    }} />
                <CurrentChat id={currentChat} />
                <MessageForm onSubmit={this.handleSubmit} />
            </Fragment>
        );
    };
}

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


  • Открыть чат 1
  • Отправить сообщение (представим, что запрос идет долго)
  • Открыть чат 2
  • Получить сообщение об успешной отправке:
    • "Сообщение в чат 2 отправлено"

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


В случае с функциональным компонентом поведение отличается:


const ChatApp = () => {
    const [currentChat, setCurrentChat] = useState(null);
    const handleSubmit = useCallback(
        (messageData) => {
            makeSomeAPICall(SEND_URL, messageData)
                .then(() => {
                    alert(`Сообщение в чат ${currentChat} отправлено`);
                });
        },
        [currentChat]
    );
    render() {
        return (
            <Fragment>
                <ChatsList changeChat={setCurrentChat} />
                <CurrentChat id={currentChat} />
                <MessageForm onSubmit={handleSubmit} />
            </Fragment>
        );
    };
}

Представьте те же действия пользователя:


  • Открыть чат 1
  • Отправить сообщение (запрос снова идет долго)
  • Открыть чат 2
  • Получить сообщение об успешной отправке:
    • "Сообщение в чат 1 отправлено"

Итак, что же поменялось? Поменялось то, что теперь для каждого рендера, для котрого отличается currentChat мы создаем новый метод. Это позволяет нам совсем не думать о том, поменяется ли что-то в будущем — мы работаем с тем, что имеем сейчас. Каждый рендер компонента замыкает в себе все, что к нему относится.


Хуки избавляют нас от жизненного цикла


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


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


  • Монтирование компонента
  • Обновление компонента (при изменении state или props)
  • Демонтирование компонента

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


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


Хук useEffect, который многими воспринимается как прямая замена componentDidMount, componentDidUpdate и так далее, на самом деле предназначен для другого. При его использовании мы как бы говорим реакту: "После того, как отрендеришь это, выполни, пожалуйста, эти эффекты".


Вот хороший пример работы компонента со счетчиком кликов из большой статьи про useEffect:


  • React: Скажи мне, что отрендерить с таким состоянием.
  • Ваш компонент:
    • Вот результат рендера: <p>Вы кликнули 0 раз</p>.
    • И еще, пожалуйста, выполни этот эффект, когда закончишь: () => { document.title = 'Вы кликнули 0 раз' }.
  • React: Окей. Обновляю интерфейс. Эй, брайзер, я обновляю DOM
  • Браузер: Отлично, я отрисовал.
  • React: Супер, теперь я вызову эффект, который получил от компонента.
    • Запускается () => { document.title = 'Вы кликнули 0 раз' }

Намного более декларативно, не правда ли?


Итоги


React Hooks позволяют нам избавиться от некоторых проблем и облегчить восприятие и написание кода компонентов. Нужно просто поменять ментальную модель, которую мы на них применяем. Функциональные компоненты по сути — функции интерфейса от параметров. Они описывают все так, как оно должно быть в любой момент времени, и помогают не думать о том, как реагировать на изменения.


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

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


  1. JustDont
    12.03.2019 19:33
    +2

    Хуки позволяют переиспользовать код

    Я конечно понимаю, что в это может быть сложно поверить, но — если вы из класса вытащите всю отчуждаемую логику, то вы точно так же будете и с классами переиспользовать код.

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


    1. kshshe Автор
      12.03.2019 19:35

      А если заменить на «Хуки позволяют удобнее переиспользовать код»?

      Зачастую ведь компоненты это не только view, взять хотя бы пример с сохранением формы.


      1. JustDont
        12.03.2019 19:44

        А если заменить на «Хуки позволяют удобнее переиспользовать код»?

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

        Зачастую ведь компоненты это не только view, взять хотя бы пример с сохранением формы.

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


        1. kshshe Автор
          12.03.2019 19:46

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

          Основное отличие в this, к которому все привязано в классах. То есть да, отделить можно, но так же ли удобно, как просто вырезать несколько строк кода из компонента?


          1. JustDont
            12.03.2019 19:59

            Основное отличие в this, к которому все привязано в классах.

            … который собственно создаёт контекст для операций. Впрочем, статика тоже никуда не делась.

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

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

            Но в общем я это всё начал как раз к тому, чтоб продемонстрировать, что разговоры «хуки лучше классов» почти кругом сводятся к «функциональное программирование лучше ООП». То есть, на самом деле, всё примерно то же самое, и упирается во вкусовщину того, у кого как мысли текут. ООПшно или функционально.


          1. staticlab
            13.03.2019 16:58

            Основное отличие в this, к которому все привязано в классах. То есть да, отделить можно, но так же ли удобно, как просто вырезать несколько строк кода из компонента?

            А в хуке фактически этот же this на компонент, но только неявный и скрытый в реализации реактовских хуков.


            1. kshshe Автор
              13.03.2019 17:14

              Хорошо, что-то, определяющее компонент есть, но разве не важно то, что здесь оно скрыто от нас и нам совсем не нужно об этом думать?


              1. staticlab
                13.03.2019 17:21

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


                1. faiwer
                  13.03.2019 19:09

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

                  Так и есть. Сложные ситуации всплывают довольно скоро. Самый такой частый и простой use case это когда в useEffect или в useCallback у нас какой-нибудь async-method есть. На момент его вызова его нутро цепляется к тому замыканию в котором оно было запущено, и соответственно к замороженным иммутабельным state-данным из useState. Как-то их обновить при этом уже невозможно, ибо замыкание есть замыкание. Внутри async-метода может быть несколько асинхронных await, и каждый из них будет в итоге обращаться к потенциально устаревшим данным.


                  Решается такое путём использования ручных useRef. Это аналог classes-properties. Но в целом момент не совсем очевидный и может очень многих поставить во временный тупик.


                  1. kshshe Автор
                    13.03.2019 20:27

                    Просто предположение: а не помогут для такого возвращаемые из эффектов unsubscribe-функции? В них можно было бы сделать что-то вроде отмены.


                    1. faiwer
                      13.03.2019 20:50

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


                      Что вы туда поместите? Ну допустим какой-нибудь флаг вида isUnmounted: bool. Ок. Как вы его туда поместите? setState? Ну ок. Вы в unsubscribeHandler вы сделали setIsUnmounted(true), и ваш компонент принудительно пере-render-ился. Ваш callbackHandler в useCallback пересоздался. Ок. Теперь у нас есть флаг isUnmounted === true. Помогло ли нам это хоть на грамм? Нет. Ведь внутри уже ранее запущенного async-метода (читайте конечного автомата в замыкании) видны только те значения которые были актуальны на момент его запуска (того самого замыкания на момент запуска). А у нас же иммутабельность и замыкания каждый раз новые. Fail. Приехали :)


                      Собственно почему useRef и ручное ref.current = ... решает проблему? А он мутабелен и сохраняет ссылочную целостность. Он вне контекста замыкания, т.к. useRef всегда возвращает одно и тоже значение. Бинго :) Привет class-properties :-D


                      1. kshshe Автор
                        13.03.2019 21:00

                        Я понял мысль, мутабельные свойства иногда действительно нужны, но я не совсем об этом. Я имел в виду создание флага в том же замыкании, вроде такого:


                        useEffect(() => {
                            let isCanceled = false;
                            const doThing = async () => {
                                // ...
                            }
                            doThing()
                                .then(() => {
                                    if (!isCanceled) {
                                        // Применяем результат 
                                    }
                                });
                            return () => isCanceled = true;
                        }, [something]);


                        1. faiwer
                          13.03.2019 21:03

                          Я пробовал так это обыграть. Но обломался. isCanceled будет выставлен в true после первого же setState в doThing. Будет rerender, а это в свою очередь приведёт к unsubscribe-у. А вернуть isCanceled в true будет уже не кому.


      1. lexey111
        12.03.2019 20:04

        Довольно спорно… эта логика невизуальная? Она должна жить в отдельном сервисе без привязки к компоненту. Асинхронная? Имеем события, подписки или какой другой стандартный путь для оповещений. Причём скорее всего даже вообще не-реакт, а чистый js/ts, переиспользуй-не хочу. RxJs, pub/sub, Redux…

        Лично у меня ощущения от использования хуков — «как бы получить возможности классовых компонентов и „взрослой“* архитектуры и при этом поменьше вникать в эту самую архитектуру». Не знаю, как по мне рано или поздно всё равно придётся разбираться, только наворочено к этому моменту будет больше и рефакторить придётся тоже больше.

        * взрослой = явное управление стейтом и потоками, разделение ответственности, DI, прочий далеко не всем нужный хлам.


        1. Koneru
          12.03.2019 21:46

          DI назвать хламом без аргументации. Сильно.


          1. lexey111
            12.03.2019 21:52

            В следующий раз не забуду поставить тэги «ирония», «сарказм» и «шутка».


    1. nsinreal
      13.03.2019 16:11

      В typescript например, все не так радужно с ООП. Реюз возможен через HoC, а он порождает огромные проблемы с типизацией. Реюз возможен через миксины, но миксины заставляют дублировать интерфейс.


      1. JustDont
        13.03.2019 16:22

        Да ну здрасьте. Что вам мешает наследовать классы в TS и делать всё без HOC? Там очень обычный ООП.

        Если вы про конкретно реакт-компоненты, то там наследование тоже никто не запрещает, если чо. Просто не рекомендуется, потому что через наследование у вас выйдет тесное связывание компонентов, в то время как через композицию можно сделать и не тесное.


        1. nsinreal
          13.03.2019 16:47
          +1

          Здрасьте.

          Наследование — это пример идеи хорошей в теории, но почему-то на практике редко используемой. Как вы думаете, почему? Потому что джаваскриптеры не умеют в ООП. Потому что наследование на практике не работает.

          Что вам мешает наследовать классы в TS и делать всё без HOC
          Например, то, что в TS нет поддержки мульти-наследования. Не считая миксинов, но см. выше.

          Мне однако безынтересно обсуждать код без кода. Если вы верите в наследование, то попрошу вас предоставить сэмпл «реюзабельного кода» на наследовании. Так, чтобы его аналогом был компонент с четырьма хукам (hooks) или четырьма хоками (HoC). Например: connect, использование двух контекстов и мемоизация коллбека.


          1. JustDont
            13.03.2019 16:56
            -1

            Потому что наследование на практике не работает.

            Так вот именно. Вы уж сначала там у себя разберитесь, что то ли HOC ужасны, то ли наследование на практике не работает. А то вы почему-то пишете про ужас-ужас, а в следующем посте у вас уже получается, что ужас-ужас — на самом деле наименьший из возможных ужасов.

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

            Угу, а концептуально мульти-наследование — штука очень интересная и способная породить бесконечно количество граблей для последующего наступания.
            Странно, чё это люди так не любят мульти-наследование в языки прикручивать?

            Мне не слишком ясен ваш изначальный посыл. В ООП плохо? А где хорошо-то тогда? Везде плохо. В TS с ООП плохо? А где на фронтэнде с ООП хорошо тогда?

            Выводы-то какие?


            1. nsinreal
              13.03.2019 17:48
              +1

              Я обожаю ситуацию, когда не понимаешь: твой собеседник тролль или вы просто друг друга не поняли?

              Краткая интерпретация событий
              Статья: Хуки позволяют переиспользовать код.

              1-е сообщение (вы): «Я конечно понимаю, что в это может быть сложно поверить, но — если вы из класса вытащите всю отчуждаемую логику, то вы точно так же будете и с классами переиспользовать код.»

              Ну вот вроде бы я должен был понять это сообщение как «реюз можно и на ООП строить, хуки не нужны».

              2-е сообщение (я): «В typescript например, все не так радужно с ООП. Реюз возможен через HoC, а он порождает огромные проблемы с типизацией. Реюз возможен через миксины, но миксины заставляют дублировать интерфейс.»

              Ну вот вроде бы вы должны были понять это сообщение как «в typescript реюз нельзя на ООП строить».

              3-е сообщение (вы): «Да ну здрасьте. Что вам мешает наследовать классы в TS и делать всё без HOC? Там очень обычный ООП.»

              Ну вот вроде я должен был понять это сообщение как «да ну шо вы говорите, в typescript на ООП отлично делает реюз»

              4-е сообщения (я): «Например, то, что в TS нет поддержки мульти-наследования. Не считая миксинов, но см. выше»

              Ну вот вроде бы вы должны были понять, что «я крайне несогласен с тем, что TS можно делать реюз на ООП, потому что отсутствует мульти-наследование»

              5-е сообщение (вы): «Угу, а концептуально мульти-наследование — штука очень интересная и способная породить бесконечно количество граблей для последующего наступания.»

              Я вот должен это как понять? Что если мульти-наследование — штука плохая, то это значит, что в typescript круто делается реюз через ООП?


              1. JustDont
                13.03.2019 18:46

                Я отвечаю на сообщения, а не на ветки, особенно когда времени прошло уже порядочно, и контекст забылся. Поэтому я, читая ваше «в typescript c ООП плохо» — понял его именно так. Что в TS с ООП плохо. А у вас плохо не в TS, а в реакте с TS, что всё-таки большая разница.

                Если вы имели в виду именно это, то имхо, 1) удельный КПД этого действия слишком мал, чтобы его всерьез рассматривать; 2) подобный реюз не «атомарен» и не прячет деталей «абстракции».

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

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

                Будет ли проще сделать свой стейт менеджмент на хуках, чем на HOC? Ну да, скорее всего. А еще проще будет вообще готовый взять, даже если это не к ночи будь помянутый редакс.


                1. nsinreal
                  13.03.2019 19:02

                  И когда вы стейт менеджмент будете делать не на lifecycle-методах, а сторонними средствами
                  Вот тут проблема есть в этом рассуждение.
                  Да, некоторые люди используют реактовский state (без state management).
                  Да, статья написана с примерами кода без state management.
                  Но это не значит, что хуки не решают каких-то проблем.
                  Реакт занимается всем view, а не только рендерингом. Сложную view-логику вы не сможете переложить на state management, потому что там ей не месте.
                  Не говоря уже о том, что react hooks упрощают использование connect, contexts и прочих штук.


                  1. JustDont
                    13.03.2019 19:14

                    Сложную view-логику вы не сможете переложить на state management, потому что там ей не месте.

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

                    Да, некоторые люди используют реактовский state (без state management).
                    Да, статья написана с примерами кода без state management.
                    Но это не значит, что хуки не решают каких-то проблем.

                    Если б они не решали никаких проблем — их бы никто и не написал, не?
                    Я вроде бы обратного и не утверждал. Я лишь комментировал чрезмерные обобщения а-ля «хуки позволяют переиспользовать код», хотя на самом деле там должно быть «хуки позволяют переиспользовать код*», а под * — много мелкого текста о границах применимости и о том, что во многих случаях с этим и без хуков всё нормально.


  1. rzcoder
    12.03.2019 20:04
    +2

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


    1. defusioner
      13.03.2019 13:46

      Похоже на Вью


    1. nuit
      13.03.2019 14:04

      создает неопределенное количество замыканий

      Создание замыканий не такая уж и проблема, а вот то что многие начнут писать код с утечками памяти из-за closure context sharing'а — это печально.


  1. zompin
    12.03.2019 20:15
    +1

    Мне очевиднее componentDidMount и componentWillUnmount, а не useEffect(() => {}). Во тором случае есть нюансы, если не передать список переменных, то он будет вообще работать как componentDidUpdate. Дело привычки конечно, но не очень очевидно.


    1. kshshe Автор
      12.03.2019 20:18

      Тут важно понимать, что useEffect не является заменой для жизненного цикла компонента. Это в принципе другая концепция, когда важен не компонент, а конкретный рендер.


  1. inv2004
    12.03.2019 20:32

    Почему во всех подобных статьях сравнение хуков с чистым реактом на стейтах, а не с react-redux?, в этом случае и стейты исчезают и всё что пишется — одна строка биндинга компонента и редукса.


    1. kshshe Автор
      12.03.2019 20:37

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


    1. nsinreal
      13.03.2019 02:49

      Потому что хуки заменяют в том числе и connect.


      1. inv2004
        13.03.2019 04:20

        По-этому и хотелось бы сравнить именно эти два подхода.


        1. nsinreal
          13.03.2019 11:11

          Ну вот примеры для redux-react-hook. Без прямых сравнений, увы. Просто, чтобы вы могли понять как это примерно выглядит.

          Выдергивание стейта:

          const Component = (props) => {
             const state = useMappedState(getStateWithMySelector);
          
             return (<></>)
          }
          


          Выдергивание стейта по параметризированному селектору:
          const Component = (props) => {
             const todo = useMappedState(useCallback(
               state => state.todos.get(props.id),
               [props.id],
             ));
          
             return (<></>)
          }
          


          Диспатчинг:
          const Component = (props) => {
            const dispatch = useDispatch();
          
            const handleClose = useCallback((event) => {
              dispatch(tryHideSnackBar({ snackId: props.id }));
            }, [props.id]);
          
             return (<></>)
          }


          1. gibson_dev
            13.03.2019 12:53

            Это же на каждый рендер будет создваться новая функция в замыкании(


            1. kshshe Автор
              13.03.2019 13:18

              Не совсем так. На каждый, где отличается props.id, а это редкие ситуации.


              1. faiwer
                13.03.2019 16:15
                +1

                Что вы, что вы. Абсолютно на каждый. Никакие props.id тут ничего не поменяют. Другое дело, что useCallback (а лучше useAutoCallback) вернёт ранее созданный метод, а не новосозданный, если props.id не был изменён.


                Все эти useEffect, useLayoutEffect, useCallback и что там ещё есть, таки действительно порождают новые методы в замыкании раз за разом. Другой вопрос, что это обычно не является критичным с точки зрения производительности.


                1. kshshe Автор
                  13.03.2019 16:17

                  Если говорить строго, то да, на каждый, конечно. Просто дальше она никуда не попадет и ни на что не повлияет.


            1. nsinreal
              13.03.2019 14:02
              +1

              Да, действительно, будет каждый рендер создаваться новая функция в замыкании — это лишняя нагрузка на GC, что совсем не радует.
              НО! То, что функция создается — еще не значит, что именно она будет использоваться. Здесь useCallback используется для мемоизации. И поэтому дочерние компоненты не будут зазря перерендериваться.


  1. theaklair
    13.03.2019 19:07

    Зачем у вас в примерах кода после
    extends React.Component
    идет знак "="?


    1. kshshe Автор
      13.03.2019 20:28

      Писал все в текстовом редакторе без обработки синтаксиса, на автомате поставил =. Спасибо за замечание, поправлю.


      1. theaklair
        13.03.2019 23:54

        Код ревью :)
        А по делу — спасибо, что делитесь с русскоязычным сообществом.


  1. botyaslonim
    14.03.2019 16:36
    +1

    Хуки позволяют писать более интуитивно-понятный код
    — ну конечно, всякий новый функционал, заметно отличный от того, к чему мы привыкли за годы, всегда объявляется «интуитивно-понятным»

    Монтирование компонента
    Обновление компонента (при изменении state или props)
    Демонтирование компонента
    Это кажется удобным, но я убежден в том, что это удобно исключительно из-за привычности. Этот подход не похож на React.

    — что???!?! Не похож на React? Базовый функционал самого Реакта?


    1. staticlab
      14.03.2019 21:03
      +1

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

      Так лишь бы не классы, ведь классы — это злое зло :) (Неважно, что в 2015 они презентовались как «Наконец-то в JS появятся классы как во взрослых языках, теперь можно будет делать ООП!»)


      1. justboris
        14.03.2019 23:31

        Не вижу противоречия. Люди, продвигавшие классы в Javascript, и авторы React-хуков – это разные люди, с разными целями и предпочтениями.


  1. taujavarob
    14.03.2019 22:37
    +1

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


    Более точно — у нас появился React.js 2.0 (если кто ещё НЕ заметил сам).

    Произошло тоже что с Angular — когда появился Angular 2.0

    Всё что вы знали и писали до этого (до React.js 16.8) можно забыть и выкинуть в мусорку.

    Никто в здравом уме НЕ будет мешать хуки и классы в одном коде.

    А нам НЕ обещают, что классы будут вообще развивать в будущем.
    Точнее, — обещают НЕ развивать классы в React.js вообще.
    Нет, их не выкинут — но смысл использовать "классовый подход" в новых проектах — исчезает. До нуля.

    Вывод — выкинуть все учебники и патерны и учить по новой хуки.

    Это будущее React.js которое уже наступило, хотите вы этого или нет.