Ліниве завантаження зображень з React Suspense та lazy

4 хв. читання

Реалізація лінивого завантаження картинок — одна зі стандартних задач по оптимізації доставляння ресурсів. Варіантів її вирішення у звичайному JS застосунку дуже багато, на будь-який смак та вміння. Можна прочитати про них на medium, codeguida та css-tricks, а нагуглити можна ще більше. В React застосунках ситуація з варіантами аналогічна, проте існують технічні вимоги самої бібліотеки від Facebook.

Повертаючись до основної теми статті, з виходом React 16.6 для розробників стали доступні нативні реалізації динамічного завантаження компонентів Suspense та lazy (лінк на документацію). Проте їхній функціонал відстежування, чи вже готовий цільовий компонент до рендеру, можна використати й в інших цілях, наприклад, для лінивого завантаження великих зображень. Як заглушку можна використовувати будь-який інший компонент, але в даній статті будемо використовувати картинку з низькою якістю. P.S. класний та економічний спосіб використання svg для заглушок описаний в статті на css-tricks.

Перейдемо до найсмачнішого — коду. Розглянемо два варіанти: класичний з використаннями state та новий зі Suspense/lazy.

Один з класичних варіантів реалізації лінивого завантаження:

class LazyImageClassical extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isHighLoaded: false
    };
  }

  componentDidMount() {
    const { highSrc } = this.props;
    this.loadHighRes(highSrc);
  }

  loadHighRes = imageSrc => {
    const image = new Image();
    image.onload = () => {
      this.setState({
        isHighLoaded: true
      });
    };

    image.src = imageSrc;
  };

  render() {
    const { isHighLoaded } = this.state;
    const { src, highSrc, alt } = this.props;

    if (isHighLoaded) {
      return <img src={highSrc} alt={alt} />;
    }

    return <img src={src} alt={alt} />;
  }
}

Тепер варіант з Suspense/lazy:

const getImageComponent = ({ src, alt }) => () => <img src={src} alt={alt} />;

const getLazyImage = ({ src, alt }) =>
  lazy(
    () =>
      new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = () => {
          resolve({
            default: getImageComponent({ src, alt })
          });
        };
        image.onerror = reject;
        image.src = src;
      })
  );

function ProgressiveImage(props) {
  const SimpleImage = getImageComponent(props);
  const HighRes = getLazyImage({ src: props.highSrc, alt: props.alt });

  return (
    <Suspense fallback={<SimpleImage />}>
      <HighRes />
    </Suspense>
  );
}

Приклад вживу можна подивитися в браузері та знайти на codesandbox Для тих, хто хоче більше контролю — вантажити картинку при появі у Viewport, можна додати логіки від IntersectionObserver (але основного flow це не змінить).

На мій погляд, класичний варіант зі state більш читабельний та лінійний, проте одним з мінусів залишається перевірка існування Компоненту (this) на image.onload. Але у кожного свій смак. В захист підходу «Suspense/lazy» можна додати, що він пасуватиме будь-якому promise-based процесу.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація