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

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

Мы столкнулись с проблемой, работая в проекте, в котором есть сложный компонент, построенный поверх реагирующей виртуализированной Grid. Давайте сначала рассмотрим базовый пример компонента virtualized-grid:

По сути, мы должны определить:

  • Как отрендерить каждую ячейку.
  • Размер всей сетки.
  • Сколько столбцов и строк в нем будет.
  • Размер каждой строки и столбца.

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

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

Предположим, нам нужно добавить к нашему VirtualizedGrid новый столбец, в котором каждая строка будет отображать настраиваемый элемент React, который показывает эскиз продукта:

Теперь высота каждой строки зависит от высоты ее миниатюрного представления. Чтобы упростить пример, мы предполагаем, что ширина миниатюрного изображения постоянна (200px).

Итак, как мы можем заранее узнать, какой высоты будет каждая строка?

Мы только что определили сценарий проблемы, о которой идет речь в этом посте: «как измерить неотрисованные элементы в React».

Измерьте неотрисованный элемент

Чтобы измерить элемент, мы используем функцию measureElement, которая в основном измеряет элемент React, отображая его в скрытом контейнере, добавленном к <body> текущего документа.

Теперь мы можем адаптировать функцию rowHeight нашего virtualizedGrid для вычисления высоты каждой строки путем измерения ее миниатюрного изображения:

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

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

Измеряйте неотрисованные элементы с помощью порталов

После реализации предыдущего решения мы получили ошибку, когда виртуальная сетка попыталась измерить <ProductThumbnailView />: компонент отображает текст i18n, поэтому его необходимо отобразить внутри i18nProvider приложения, чтобы получить текущую локаль.

Мы можем адаптировать функцию measureElement, чтобы она отображала элемент внутри контекста приложения, а не добавлялся в тело документа. Сделаем это через React Portals.

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

Затем мы адаптируем функцию measureElement, чтобы она отображала элементы в этом новом контейнере:

Теперь мы можем, наконец, измерить <ProductThumbnailView /> компонентов, так что мы можем <VirtualizedGrid /> адаптировать высоту строк к их содержимому.

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

Подводя итоги

Чтобы измерить неотрисованный элемент, который должен быть помещен в корень приложения или в любой текущий узел DOM, мы должны:

  • Поместите скрытый контейнер внутри узла DOM, где нам нужно отобразить элемент для измерения.
  • Реализуйте указанную выше функцию measureElement, которая отображает элемент внутри узла DOM с помощью React Portal, измеряет его и размонтирует элемент перед возвратом его размера.

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