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

Грубый подход

Один из подходов состоит в том, чтобы найти количество символов, которое помещается в ширину контейнера. Вот пример фрагмента кода:

import React, { useState, useEffect } from 'react';
import DropDown from './DropDown';

const Activity = (props) => {
  const { content } = props;
  const [canDisplayDropDown, setCanDisplayDropDown] = useState(false);
  
  const setDropDownStatusByWidth = (content) => {
     let width = document.getElementsByClassName('container')[0].offsetWidth;
     const allowedLimit = width / 6; //I took the default divisor as 6 based on a character's width
     if (content.length > allowedLimit) {
       setCanDisplayDropDown(true);
     }
  };
   
  useEffect(() => {
    setDropDownStatusByWidth(content);
  });

  return (
    <div className="container">
      <div className="details">
          <span>
            {content}
          </span>
      </div>
        {canDisplayDropDown && <DropDown />}
    </div>
   );
 };

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

CSS-подход

Мы можем улучшить это с помощью CSS. Установив white-space: nowrap, text-overflow: ellipsis и overflow: hidden в классе details, мы можем отображать многоточие, когда содержимое переполняется. Оставшаяся проблема заключается в том, чтобы прикрепить раскрывающийся список рядом с содержимым, если оно переполняется.

.details {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

Подход с использованием LayoutEffect

Другой подход заключается в использовании useLayoutEffect, крючка React, который помогает выполнять измерения и мутации DOM. Мы можем создать ссылку и добавить ее к элементу DOM, содержащему контент. Затем мы проверяем, меньше ли clientWidth scrollWidth. Если это так, содержимое переполняется, и мы можем соответствующим образом обновить статус раскрывающегося списка. Вот пример фрагмента кода:

Вот пример кода

import React, { useLayoutEffect } from 'react';
import DropDown from './DropDown';

const Activity = (props) => {
  const { content } = props;
  
  const ref = React.createRef();
  const [canDisplayDropDown, setCanDisplayDropDown] = useState(false);
  
  useLayoutEffect(() => {
    if (ref.current.clientWidth < ref.current.scrollWidth) {
      setCanDisplayDropDown(true);
    }
  }, [ref]);
  
  return (
    <div className="container">
      <div ref={ref} className="details">
          <span>
            {content}
          </span>
      </div>
        {canDisplayDropDown && <DropDown />}
    </div>
   );
 };

export default Activity;

Как это работает?

Здесь задействованы две концепции.

  1. Рефы
  2. useLayoutEffect

Мы создаем ссылку и добавляем ее в элемент DOM. Из документации по реакции: React назначит свойство current элементу DOM при монтировании компонента и назначит его обратно null при размонтировании. ref обновления происходят до componentDidMount или componentDidUpdate методов жизненного цикла.

UseLayoutEffect срабатывает синхронно после мутации DOM до того, как браузер сможет отрисовать. Мы могли бы найти DOM с содержимым через ref и определить, меньше ли clientWidth, чем scrollWidth, это вернет true, когда содержимое переполнится. Затем мы можем соответствующим образом обновить статус раскрывающегося списка.

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

Заключение

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