React 17: Ничего нового?

Автор оригинала: Dan Abramov and Rachel Nabors
  • Перевод

Disclamer

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

Ничего нового?

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

Многообещающий Concurrent Mode не будет представлен в 17 версии, как и другие нововведения, над которыми активно работает команда. Грядущий релиз является частью стратегии постепенных (частичных) обновлений.

Постепенные обновления

В течение последних 7 лет обновления React были в духе "все или ничего". Либо обновляемся до новой версии, либо остаемся на старой. Порой, необходимо было менять что-то в кодовой базе, как, например будет с устаревшим Context API, который не получится перенести автоматически.

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

Первый из вариантов будет таким же как и прежде: обновляем все приложение разом. Но теперь будет опция обновить приложение по частям. Например, большую часть приложения легко перетащить на новую версию (React 18, или React 19), тогда как какое-нибудь загруженное через lazy-подход диалоговое окно или часть приложения под определенном роутом можно будет оставить на React 17.

Команда приготовила репозиторий, который демонстрирует как загружать асинхронно (lazy-load) более старые версии React. В примере используется приложение на React 17.0.0-rc.0, которое подгружает компоненты, написанные с помощью устаревших приемов, работающие на React 16.8

Список изменений в React 17

Изменение в делегировании событий

Реакт с самой его первой версии меняет способ привязки событий, например onClick, к DOM-элементам. Реакт автоматически использует прием делегирования событий и привязывает все события к объекту document. Таким образом, достигается повышение производительности.

Однако, в случае, если несколько версий React используется на странице, у вас микрофронтенды или все еще часть функционала работает на jQuery, возникают проблемы. Такое поведение ломает event.stopPropagation(): если вложенное дерево остановило распространение (propagation) события, внешнее дерево все равно получит его. Это делает сложным работу в случае вложенных нескольких версий React. Команда популярного редактора Atom столкнулась с такой проблемой.

Теперь все обработчики крепятся к корневому элементу, а не объекту document:

const rootNode = document.getElementById('root'); // <-- вот сюда
ReactDOM.render(<App />, rootNode);

Убран костыль с Синтетическим Событием (SyntheticEvent Even Pooling)

В 17 Реакте убрана оптимизация событий, которая более не актуальна в современных браузерах.

function handleChange(event) {
  // это работает в 16 React только если добавить event.persist()
  setData(data => ({
    ...data,
    // This crashes in React 16 and earlier:
    text: event.target.value
  }));
}

Теперь такой код не будет валиться с ошибкой, и нет нужды писать event.persist()

Для обратной совместимости эта функция оставлена в качестве заглушки. Разработчики попробовали это на существующем коде в Facebook и увидели отсутствие регрессий. Возможно, это обновление еще и исправило какое-то количество багов!

Ближе к браузерам

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

  • Событие onScroll больше не всплывает, чтобы избежать текущей путаницы;

  • События onFocus и onBlur изменены "под капотом" на нативные focusin и focusout;

  • onClickCapture и другие Capture-события теперь используют браузерные обработчики событий.

useEffect() теперь полностью асинхронный

useEffect(() => {
  // This is the effect itself.
  return () => {
    // This is its cleanup.
  };
});

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

Для синхронной работы, можно по-прежнему использовать useLayoutEffect(), который остался незатронутым.

Ошибки при возвращении undefined

Это изменение довольно минорно, но повышает консистентность ошибок.

Если раньше нельзя было возвращать undefined только в обычных компонентах, то теперь такая ошибка будет выбрасываться еще и в React.forwardRef и React.memo.

let Button = forwardRef(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

let Button = memo(() => {
  // We forgot to write return, so this component returns undefined.
  // React 17 surfaces this as an error instead of ignoring it.
  <button />;
});

Улучшенный стек вызовов при ошибках

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

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

В 17 React используется новых механизм по генерации стека вызовов, что позволяет увидеть дерево React-компонентов, которое привело к ошибке, даже в production-среде.

Удаление приватных экспортов

Последнее изменение — это удаление некоторых внутренних экспортов, которые ранее были открыты наружу. Например, React Native for Web ранее зависела на некоторых внутренностях системы событий, но эта зависимость не была надежной.

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

Также был удалены ReactTestUtils.SimulateNative методы. Они не были документированы, но теперь их не будет вовсе.

Changelog

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

В новой версии React также включено 5 изменений в React, 37 изменений в React Dom, пару изменений в React DOM Server, одно изменение в React Test Rerender.

А что с Concurrent Mode?

По-прежнему, этот режим имеет статус экспериментального. В 17 React было исправлено множество багов, удалены одни unstable_ методы, и добавлены новые. Пока для продакшена использовать его рано, но потыкать определенно можно и нужно. Например, есть библиотека для работы с Firebase, reactfire, разработчики которой сделали основную версию зависимой от Concurrent Mode. К сожалению, репозиторий кажется заброшенным последние несколько месяцев. Надеюсь, это исправится.

Средняя зарплата в IT

110 500 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 7 138 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

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

    +11
    Люблю наблюдать, как натягивание какой-то абстракции на имеющийся домен внезапно рушит вещи, которые в абстракцию не очень вписываются. Это даже не про реакт 17, а про реакт вообще. Скажем, вот есть у нас модель, схематично описываемая объектом со структурой a.b.c, и соответствующие вкладывающиеся друг в друга реакт-компоненты A, B, и С. Что можно сделать в парадигме реакта, чтоб перерисовать B и только B? А ничего. Даже не смотря на то, что при неизменности C не произойдет собственно обновления соответствующего узла DOM, это очень слабое утешение, потому что море бесполезной работы (рендер в VDOM, сравнение VDOM и DOM) всё равно будет безусловно проделано. Что очень даже больно, если у вас не один C, а тысячи.

    Всё, что может предложить в ответ реакт — это «делите компоненты», и вот мне уже нужно иметь компонент «обертка (списка) C», который не имеет никакого смысла для моей модели UI и нужен исключительно для того, чтоб реакт мог «догадаться», что эта часть дерева компонентов не поменялась. Просто потому, что концепция частичных изменений (которую очень легко реализовать с vanillaJS, потому что взаимодействие с DOM идёт по отдельным элементам, очень легко не трогать тот DOM, который трогать не надо) в реакте просто не ночевала.

    И это я даже не говорю про то, что даже после всего дробления всё равно нужно выполнять довольно неочевидные действия в модели (новый объект b, указывающий на старый (список) c) просто потому, что реакт предлагает исключительно referential equality в деле сравнения пропсов.

    ЗЫ: А, так вот. Вангую, что с concurrent mode масса реактопрограммистов просто будет дергать эти самые перерисовки длинных списков везде, где не надо (как это сейчас уже происходит), и будет радоваться тому, что браузер больше не подвисает и вообще всё шоколадно. А юзеры на не очень крутых машинах будут наблюдать, как у них куски страниц неторопливо и торжественно обновляются по частям туда-сюда прямо на глазах. Зато браузер не подвисает, ага.
      0

      Строго говоря, с помощью React и соответствующего драйвера можно рисовать что угодно и где угодно. Хоть на Canvas, хоть в командной строке. Но это все в теории, а на практике у нас SyntheticEvent и Concurrent Mode.


      Я думаю, в каком-то новом React будет более близкое API к браузеру. Может быть даже и работа напрямую с DOM там, где это необходимо. Shadow DOM, опять же, пока React обходит стороной.


      Одно радует, что API React меняется, не держится сильно за старое, и есть надежда на светлое будущее =)

        0
        Или в каком-то новом браузере будет вменяемое API близкое к React, с реализацией на расте.
        0
        Что можно сделать в парадигме реакта, чтоб перерисовать B и только B? А ничего.


        Ну ты можешь убрать лишние рендеры, если тебе это нужно
        const Header = ({title}) => {title}
        export default React.memo(Header);
          –3
          Это и есть создание никому (кроме реакта) не нужных обёрток.
            +5

            Вы так говорите, как будто в этом есть что-то плохое.
            Все программирование — это обертки. Обертки над обертками. Или абстракции.


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

              +1
              Таки да, в этом очень много плохого. Когда обертки порождены невыразительностью абстракций самого фреймворка — это говорит только о том, что фреймворк не так уж и хорош.
              Это особенно хорошо видно, стоит только лишь взять любой нормальный FRP (от того же MobX или RxJS до, скажем, той реактивности, что из коробки предлагает Svelte), и обнаружить, что с нормальными абстракциями лишние обертки не появляются; да и описывать изменения моделей без принудительной referential equality гораздо легче.

              Конкретно с React.memo ситуация вообще интересная — если просто почитать документацию в контексте всей остальной документации реакта (настоятельных рекомендаций делать «глупые» компоненты, выносить работу с данными, и так далее) — становится очевидным, что вообще-то согласно всё той же документации React.memo следует применять везде, а ситуации, где он таки не используется, должны быть редкими исключениями. А не наоборот. Это вообще явно указывает на кривизну изначально заложенных в фреймворк абстракций.
                +2
                Простите за оффтоп, но ждал появления Svelte еще в первом вашем комменте. Когда вижу кого-то, кто цепляется к каким-то незначительным недостаткам ведущей тройки фреймворков (на уровне вкусовщины), а потом огромными комментами с использованием множества аббревиатур и англицизмов пытается раздуть их до астрономических размеров, понимаю — передо мной поклонник этого молодого фреймворка)
                  0
                  незначительным недостаткам

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

                    А напишите статью про кривые идеи в основе реакта!
                    Было бы интересно это еще и подкрепить мотивацией авторов для добавления таких кривых идей. Для большей объективности.


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

                        +2
                        Посмотрел по-диагонали. Этот странный человек борется с пробросом пропсов написанием странного костыля, вместо того, что-бы использовать React.Context. И где-то походя ругает редукс.
                        Вопрос что произойдет со ссылкой на компонент после анмаунта, как я понял, не рассматривается даже.
                          0
                          «Решение», которое автор предлагает — довольно кривое, и его за это уже там покритиковали все кому не лень. Но я не про решение, а про проблему, которую он описывает, если уж мы говорим о проблемах реакта. Проблема описывается самая что ни на есть животрепещущая.

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

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