company_banner

Анимированное руководство по базовым механизмам React

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



Запуск React-приложения


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

React, при первом запуске приложения, автоматически монтирует класс App к корневому контейнеру приложения.


Первое монтирование <App/>

Virtual DOM и алгоритм сравнения


В ходе работы подсистемы React, реализующей алгоритм сравнения (Diffing Algorithm), выполняется поиск различий между двумя виртуальными DOM (Virtual Document Object Model, виртуальная объектная модель документа). Притормозим ненадолго. Две виртуальные DOM? Вроде бы виртуальная DOM в React только одна… Разберёмся с этим. React выполняет сравнение предыдущей виртуальной DOM с новой. Обновление браузерной DOM производится только в том случае, если при сравнении виртуальных DOM выявлены различия между ними.


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

Рассмотрим то, что происходит на вышеприведённой анимации.

  • По событию click выполняется вызов API.tweet() с данными POST-запроса, содержащими message.
  • В ответ на запрос возвращается payload, эти данные поступают в коллбэк (event) => { … }.
  • Если данные, возвращённые в payload, должны вызывать изменение props — выполняется сравнение деревьев виртуальных DOM.
  • Если деревья оказываются различными — в браузер отправляется самое свежее дерево.
  • Затем новая виртуальная DOM становится старой, а мы ожидаем новых событий.

Компоненты React


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

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

Метод render() всегда должен оставаться чистой функцией


Метод render() обновляет виртуальную DOM компонентов. Если новое дерево виртуальной DOM отличается от ранее выведенного дерева, то React, помимо обновления виртуальной DOM, обновит и реальную DOM браузера. Разработчик не должен самостоятельно выполнять непосредственное обновление браузерной DOM. Это правило относится к любым местам в коде React-приложения. Особенно оно важно в применении к функции render().


Не загрязняйте метод render() вызовами функций, которые каким-то образом обновляют DOM напрямую

В методе render() не следует изменять состояние компонента (даже с использованием setState), выполнять HTTP-запросы. Не обращайтесь из этого метода к jQuery, не выполняйте запросы на загрузку неких данных. Дело в том, что метод render() нужно поддерживать в таком состоянии, в котором он представлял бы собой чистую функцию. Этот метод всегда вызывается на заключительном этапе работы механизмов компонента. В ходе его выполнения нужно лишь произвести обновление пользовательского интерфейса. При этом предполагается, что все обновления виртуальной DOM уже выполнены.

События жизненного цикла компонентов


Когда компонент впервые монтируется в DOM, React вызывает событие его жизненного цикла componentWillMount. После того, как виртуальный компонент в первый раз выводится на экран (то есть — впервые монтируется в реальную DOM браузера), вызывается ещё одно событие — componentDidMount.

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

Итоги


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

Уважаемые читатели! Пользуетесь ли вы хуками React?
RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 34

    0
    В методе render() не следует изменять состояние компонента (даже с использованием setState), выполнять HTTP-запросы. Не обращайтесь из этого метода к jQuery, не выполняйте запросы на загрузку неких данных.


    Кто-то использует React с jQuery???
      0
      Как минимум, при постепенной миграции с jQuery на React они вполне могут какое-то (потенциально неограниченное) время сосуществовать.
        +1
        Я. И очень много людей сидящих на плагинах с jquery за основу.
        +2
        Пока составлялось это руководство, componentWillUpdate() и componentWillMount() успели уйти в UNSAFE :{
          0
          Пока составлялось это руководство классовые компоненты с жизненным циклом по-тихоньку уходят в «UNSAFE» с ноября прошлого года
            +2
            Думаю, для HOC, аггрегирующих большое количество других компонент в себе, чтобы не утонуть в useState/useEffect/useCallback полотнах, все же будут использоваться компоненты с их жизненными циклами
              +1

              Я бы не согласился. Теперь стало наоборот проще в рамках функционального компонента с хуками избавиться от спагетти-кода и изолировать независимый код друг от друга в одном компоненте.

              +1

              Говорить, что они уходят в UNSAFE неправильно, ибо их не депрекейтили и пока даже не планируют.

                0
                github.com/facebook/react/blob/master/CHANGELOG.md#1690-august-8-2019
                они уже deprecated и будут убраны в 17 версии
                  0

                  Задепрекейчены только componentWill{Mount,ReceiveProps,Update}, классы никуда не уходят.

                    0
                    Только сейчас доехал о смысле поста, на который отвечал. Конечно, классы никуда не уходят и не уйдут в ближайшее время)
            +1
            Метод render() всегда должен оставаться чистой функцией

            Судя по отсутствию аргументов эта функция должна всегда давать один и тот же результат, независимо от стейта.

              0

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

                0

                Только, если этот this будет иммутабельным, что, очевидно, бесполезно.

                  0

                  Эдак надо требовать, чтобы любой переданный в чистую функцию объект был иммутабельным, чего никто обычно не делает.


                  Обычно всё-таки считается достаточным чтобы функция не изменяла this сама, и возвращала то же самое значение при том условии, что this не изменил никто другой.

                    0
                    чего никто обычно не делает.

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

                      0

                      Так я и не спорю. Вот только в определении присутствует понятие "одинаковости", которое можно понимать разными способами.


                      Так вот, если потребовать ссылочной эквивалентности, то окажется что даже простейшая функция вроде x => [x] не является чистой, из-за чего определение становится практически бесполезным.


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

                        0

                        А ещё можно пофантазировать о том, что Math.random() — тоже чистая функция, так как между разными вызовами Math — это структурно разные объекты.

                          0

                          Нет, нельзя, потому что Math — пространство имён, а не полноценный объект; у Math нет состояния.


                          И даже если бы у Math было состояние — Math.random его менял бы.

                            –1

                            Что вы, выражение this.x += 1 — ничего не меняет, это же просто синтаксический сахар для создания нового this, который неявно возращается нашей безусловно чистой функцией.

                              0

                              Вот как раз "неявный возврат значения" и называется побочным эффектом.

                                0

                                Редко случается когда Дмитрий неправ. Конструкции вида this.x ±= 1 нерекомендовано использовать. Это скорее соль, чем сахар:)

                    0

                    Давайте зайдём с другой стороны. Вот у нас есть функция render():


                    render() {
                        return <span>count = {this.state.count}</span>
                    }

                    У нас же javascript, и мы её всегда можем вызвать вот так:


                    render.call(Object.freeze({
                        state: Object.freeze({
                            count: 5,
                        }),
                    }));

                    При таком вызове мешает ли что-то считаться функции чистой?

                      –1
                      Object.defineProperty(Object, 'freeze', {
                        enumerable: false,
                        configurable: false,
                        writable: false,
                        value: () => alert("hola")
                      });

                      а при таком коде мешает ли что-то считаться функции, описанной выше, чистой?)
                0

                Я вот недавно рассказывал людям, что «теплый ламповый» реакт с классами и методами — это как геоцентрическая вселенная Птолемея с эпициклами, а хуки — это как гелиоцентрическая система Коперника. Известно, что если взять предел по всем эпициклам Птолемея, то результат получится точно таким же, как у Коперника (и совпадет с наблюдаемым миром). Но только коперниковская система неизмеримо проще и логичнее.


                Точно так же и с хуками: магическим образом от трети до половины кода просто исчезает при рефакторинге с классов на хуки. Это какой-то очень фундаментальный закон мироздания был открыт.


                Так что нет тут никакого «используете ли вы хуки». Есть «весь ли код вы уже перевели на хуки, или пока еще в процессе».

                  0

                  А сколько кода исчезнет при переходе с React на $mol...

                    0

                    Сколько?

                      –1

                      Половина так точно.
                      https://github.com/eigenmethod/todomvc/blob/master/examples/react/js — 10KB
                      https://github.com/eigenmethod/todomvc/blob/master/examples/mol — 5.5KB


                      Ну вот взять в пример использование глупого компонента:


                      <TodoItem
                          key={todo.id}
                          todo={todo}
                          editing={this.state.editing === todo.id}
                          onToggle={this.toggle.bind(this, todo)}
                          onDestroy={this.destroy.bind(this, todo)}
                          onEdit={this.edit.bind(this, todo)}
                          onSave={this.save.bind(this, todo)}
                          onCancel={this.cancel}
                      />

                      Аналог на $mol:


                      Item!id $my_todo_row
                          todo?val <=> todo!id?val
                          editing?val <=> todo_editing!id?val

                      Ну или, что то же самое:


                      @ $mol_mem
                      Item( id : string ) {
                          return $my_todo_row.make({
                              todo : val => this.todo( id , val ) ,
                              editing : val => this.todo_editing( id , val ) ,
                          })
                      })
                        0

                        Не знаком глубоко с $mol, но выглядит так, будто в react версии есть всякие onSave, onCancel, в $mol версии такого не вижу

                          0

                          Потому что они для реализации той же функциональности не нужны.


                          Вместо onSave этот компонент дёргает this.todo({ completed , title }), а вместо onCancelthis.editing( false ).


                          Благодаря двусторонним каналам API компонента получается гораздо проще, без 100500 колбэков для управления им.

                            0
                            С таким же успехом можно запихнуть все данные и callback'и в один объект и передать в react компонент и сократить все до:
                            <TodoItem todo={todoGodObject} />
                            Ваш пример на самом_лучшем_фреймворке ничем не лучше такого подхода.
                              0

                              От того, что вы часть кода переложите из одного места в другое, его меньше не станет. Скорее даже наоборот.

                    0

                    С одной стороны я с вами соглашусь — на хуках код писать удобнее и проще. Но кидаться переписывать рабочий код я бы не стал — мало ли что там пойдёт не так. Работает — и пусть работает.

                      0

                      Уточнение: проста и логична система Кеплера, которая с эллипсами. А у Коперника, при попытках натянуть его систему на реальные наблюдаемые данные, получались всё те же эпициклы.

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

                    Самое читаемое