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

Примечание: на момент написания кода это было сделано с использованием загрузчика harmony jsx, который имеет подмножество функций ES6. Основные функции ES6, которые я использую, - это стрелочные функции и обозначение классов для компонентов.

Решение:

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

Как мы можем выяснить, не все ли предметы помещаются в контейнер?

  1. Нам нужно знать, когда изменяется размер окна, чтобы мы могли определить, следует ли нам обрезать больше или меньше.
  2. Нам нужно знать ширину контейнера.
  3. Нам нужно знать ширину каждого элемента.
  4. Если общая ширина элементов больше, чем у контейнера, то мы знаем, что нам нужно усечь наш список.

1. Прослушивание события изменения размера окна

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

Примечание. Событие изменения размера происходит в ОКНЕ, а не в ДОКУМЕНТЕ. Например:
window.addEventListener('resize', this._handleWindowResize)
вместо
document.addEventListener('resize', this._handleWindowResize)

Итак, когда мы должны добавить этого слушателя? Имеет смысл добавлять этот слушатель изменения размера только тогда, когда DOM смонтирован и компоненты правильно рассчитали свою ширину и высоту. Поэтому мы добавляем этого слушателя в componentDidMount():

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

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

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

2. Ширина контейнера.

Благодаря встроенному в React атрибуту ref, который он предоставляет, определить ширину любого узла DOM очень просто: React.findDOMNode(this._containerTarget).offsetWidth

Что такое this._containerTarget? Это простая ссылка на наш контейнер div, который мы создали с помощью встроенного атрибута ref.

Вот код для создания ссылки:

Когда у нас есть ширина, мы устанавливаем состояние и сохраняем ширину:

Теперь, когда функция создана, я привяжу ее в конструкторе следующим образом:

3. Ширина каждого отдельного предмета

Вторая задача - найти ширину каждого элемента - тривиальна и зависит от вашего CSS. Вы можете установить постоянную ширину в вашем файле компонента и при необходимости получить к нему доступ: var ITEM_SIZE = 60

4. Обрезать список элементов

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

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

Если это число больше, чем количество элементов, мы ничего не делаем и оставляем список как есть. Однако, если это не так, нам нужно усечь список элементов. Например, если список в настоящее время может вместить 8 элементов и есть 10 элементов, тогда покажите 7 элементов и используйте 8-й элемент, чтобы отобразить оставшееся количество элементов (например, +3):

Где параметр items - это массив элементов реакции, которые мы хотим отобразить.

Как будет работать этот метод? Поскольку setState запускает повторный рендеринг, при изменении размера нашего окна и обновлении состояния ширины контейнера запускается _truncateItems.

Наша функция рендеринга будет выглядеть примерно так:

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

Оптимизация

В React DOM отображается каждый раз, когда состояние изменяется для обновления компонентов и их дочерних элементов. Поскольку событие изменения размера изменяет состояние при каждом изменении размера окна, было бы излишним разрешать _handleWindowResize вызывать каждый раз, когда это происходит. Вместо этого мы используем debounce, чтобы выделить вызовы изменения размера нашего контейнера. Debounce позволяет _handleWindowResize запускаться только по прошествии определенного времени с момента последнего вызова этой функции. Чтобы реализовать это, я решил использовать дебаунд библиотеки подчеркивания с задержкой в ​​100 мс. Это помогает избежать повторного рендеринга наших компонентов несколько раз при изменении размера одного окна.

Итак, куда мы помещаем наш код отладки? Помните, как мы связали _handleWIndowResize в нашем конструкторе? this._handleWindowResize = _.debounce(this._handleWindowResize.bind(this), 100);

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

Тестирование

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

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

«Может обновлять только смонтированный или монтируемый компонент. Обычно это означает, что вы вызвали setState () на размонтированном компоненте. Это запретная игра. Пожалуйста, проверьте код на наличие неопределенного компонента ».

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

С помощью этого довольно тривиального обходного пути я использую переменную экземпляра, чтобы отслеживать, когда компонент монтируется. Поскольку обратные вызовы ref вызываются до componentDidMount, он будет запускаться _handleWindowResize при монтировании, и мы сможем увидеть наш список.

Вот полный код и рабочий пример в jsfiddle для справки:
// js

// css

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

Заключение

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

Daniel был совместной командой Hootsuite в течение последних 8 месяцев, внося ключевой вклад в новые пользовательские утверждения и новую бета-версию Bulk Composer. Ему очень понравилось работать с React, чтобы эффективно создавать и проектировать компоненты интерфейса для сервисов, к которым они будут подключаться. Он с нетерпением ждет возможности применить навыки, полученные в этом кооперативе, в своей карьере в будущем.