Изначально размещен в моем личном блоге.

Хотите увидеть что-нибудь дикое? 🦒👀

Малоизвестный метод

В API класса компонентов React есть метод, который малоизвестен и используется редко: forceUpdate(). Как говорится в документации React,

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

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

И один из особых способов сделать это - позвонить forceUpdate().

Давайте посмотрим, как это может выглядеть:

Да, я знаю, что код плохой, но это всего лишь

a̶ ̶r̶e̶f̶l̶e̶c̶t̶i̶o̶n̶ ̶o̶f̶ ̶m̶y̶ ̶d̶a̶i̶l̶y̶ ̶c̶o̶d̶e̶ ̶p̶r̶o̶d̶u̶c̶t̶i̶o̶n̶

пример. На самом деле он ничего не делает.

ᴋɪɴᴅᴀ

Вот как выглядит выполнение приведенного выше кода:

Следует помнить, что forceUpdate() пропустит shouldComponentUpdate() (который по умолчанию просто возвращает истину).

Возвращаясь к документации React:

Вызов forceUpdate вызовет render() для компонента, пропуская shouldComponentUpdate(). Это запустит обычные методы жизненного цикла для дочерних компонентов, включая метод shouldComponentUpdate() каждого дочернего компонента. React по-прежнему будет обновлять DOM только в случае изменения разметки.

Последнее предложение просто означает, что вызов forceUpdate() может оказаться no-op функцией, например () => {}. Если ничего в DOM не изменится из-за принудительного повторного рендеринга, значит, вы потратили зря работу над потоком пользовательского интерфейса.

А мы этого не хотим.

Используйте forceUpdate() осторожно и намеренно

forceUpdate (), перехватчики React hway

Если вы использовали хуки в React, то наверняка заметили, что:

  1. Они классные 😎
  2. У них нет следствия крючка для componentDidCatch() или forceUpdate() (эй, что дает ??)

Хорошо, может быть, вы не заметили пункт 2. Это хорошо, хорошо, хорошо.

Возможно, это не первоклассный крючок, но это нас не остановит! Напишем собственный хук!

Итак, что делает этот хук?

Сначала он вызывает React.useState() и ничего не передает, или undefined, в начальное состояние. Обратите внимание, однако, что он не заботится о первом аргументе, возвращаемом в кортеже useState, который является переменной состояния. Мы скоро увидим, что это нормально, потому что наше начальное состояние - falsey, что просто забавно сказать, что оно оценивается как false. Важно отметить, что мы фактически сделали переменную состояния частной для настраиваемой ловушки или скрытой для внешние пользователи. Переменная состояния все еще существует, но мы просто не открываем ее для использования. Вот что происходит в React DevTools:

Последнее, что делает ловушка useForceUpdate(), - это возвращает мемоизированный обратный вызов, который просто переключает безымянную переменную состояния. Как и в методе setState() компонента класса React, вы можете передать функцию обновления сеттеру React.useState(), как я сделал здесь. Эта функция обновления просто возвращает то, что в настоящее время является безымянной переменной состояния, например false ➡️ true.

Если вы не уверены, что я имею в виду под мемоизированный обратный вызов, ничего страшного. В этом случае мы просто хотим, чтобы функция () => { forceUpdate(); } всегда ссылалась на один и тот же базовый объект. ссылка. Итак, мы кэшируем или запоминаем, что это за функция, даже через отдельные вызовы useForceUpdate(). Если вам интересно узнать больше о мемоизации, ознакомьтесь с этой статьей в Википедии.

Чтобы ответить на вопрос «Что делает этот перехватчик?», он позволяет вам принудительно выполнить повторный рендеринг в вашем функциональном компоненте без фактического изменения props, state или context.

Однако я вас немного ввел в заблуждение. Ловушка useForceUpdate() не совсем однозначное сравнение с использованием компонента класса forceUpdate() выше. Это потому, что нет shouldComponentUpdate() зацепа. Хуки требуют другой ментальной модели React.

Время для примера! ⏰

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

Во-первых, давайте вернемся к тому, что вызывает повторный рендеринг компонента React. Как я упоминал ранее, компонент будет повторно отрисован, если:

  • его shouldComponentUpdate() возвращает true
  • собственные внутренние state изменения
  • он получает новый props
  • context он подключается к изменениям
  • это class компонент, вызывающий forceUpdate()

Обратите внимание, что изменение refs в React не вызывает повторного рендеринга. Ссылки хороши для императивного изменения узлов DOM. Я в основном использую их для управления фокусом: ref.current.focus(). Но вы также можете использовать их для хранения переменных, подобных экземпляру, даже в функциональных компонентах! Помните, что изменение таких переменных, подобных экземпляру, не приведет к повторной визуализации.

Далее я хочу вкратце рассказать об одном из моих любимых хуков: React.useEffect(). Говоря кратко, я имею в виду ссылку на твит Райана Флоренс.

Теперь, когда это решено, давайте посмотрим код, Коди! 🤓

И вот он, запускается (((W I L D))):

Странный компонент UseTheForce, отрендеренный во всей красе.

Давайте разберемся 💃

Сначала мы получаем обратный вызов forceUpdate():

// get the forceUpdate() callback by calling the useForceUpdate() hook
const forceUpdate = useForceUpdate()

Теперь давайте возьмем ref для счетчика псевдо-переменной-экземпляра, инициализированного значением 0:

// let's retain some counting state without useState()... with refs!
// initialize the ref.current value to 0
const renderCount = React.useRef(0)

Используя наше удобное руководство для денди n̶o̶t̶e̶b̶o̶o̶k̶ ̶📕̶ React.useEffect() от Райана Флоренса, давайте увеличим счетчик на каждом рендере:

// add one to the render count ref's current value on each render
React.useEffect(() => {
  renderCount.current += 1
}) // don't pass any dependency array here, not even empty list []

Для <button> необходим простой обработчик кликов:

// When I click, you... don't click, just force the update!
const onClick = React.useCallback(() => {
  forceUpdate()
}, [forceUpdate])

И напоследок пример разметки:

// render the button and our "state variable" render count
return (
  <>
    <button type="button" onClick={onClick}>
      Use the Force 👋
    </button>
    <div>Render count: {renderCount.current}</div>
  </>
)

Заключение

TL; DR: не делайте этого. *

Это определенно было упражнением в а что, если? территория. Хотя, безусловно, существуют варианты использования для forceUpdate(), скорее всего, вам лучше использовать хуки props, state и context для декларативного рендеринга того, что вы хотите и когда.

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

Опять же, используйте forceUpdate() осторожно и намеренно.

И если вы думаете о чем-то вроде описанного выше крючка useForceUpdate(), то вы, вероятно, неправильно его держите.

Спасибо, что пришли на мой выступление на TED. Ведь все крутые ребята этим занимаются.

* Если у вас нет уважительной причины для этого. T̶e̶r̶m̶s̶ ̶a̶n̶d̶ ̶c̶o̶n̶d̶i̶t̶i̶o̶n̶s̶ Применяются нюансы.