меню

Пользователь загружает дюжину изображений в редактор галереи. Каждое изображение получает предварительный просмотр в реальном времени URL.createObjectURL(). Сессия длится двадцать минут, предварительные просмотры меняются по мере добавления и удаления файлов, и использование памяти вкладкой постоянно растет — потому что каждый createObjectURL() вызов должен быть связан с другим вызовом revokeObjectURL(), и эта связь должна сохраняться при перерисовке, изменении свойств и размонтировании. Большинство компонентов работают неправильно, и ошибка незаметна, пока кто-то не оставит вкладку открытой достаточно долго, чтобы заметить, как начинает расти скорость обновления.

useObjectUrl Решение состоит всего из одной строки: передайте ему File, Blob, или MediaSource, получите в ответ строку URL и revokeObjectURL больше никогда не вызывайте себя. Всё, что ниже, — это настоящий @reactuses/coreAPI, написанный в первую очередь на TypeScript.

Ручная версия и места утечек

URL.createObjectURL() API браузера — это то, что лежит в основе каждого предварительного просмотра файлов на стороне клиента: передайте ему Blob (File например), и он вернет blob: URL-адрес, который вы можете сразу же вставить в <img src> или <video src>. Подвох в другом конце — каждый созданный им URL-адрес остается активным, храня ссылку на базовые данные, пока вы явно не вызовете URL.revokeObjectURL(). Забудьте об этом вызове или вызовите его в неподходящий момент, и URL-адрес — и память, связанная с ним — никогда не будет освобожден.

В компоненте React легко попасть в "неподходящее время":

	
function FilePreview({ file }: { file?: File }) {
  const [url, setUrl] = useState<string>();

  useEffect(() => {
    if (!file) return;
    const next = URL.createObjectURL(file);
    setUrl(next);
    return () => URL.revokeObjectURL(next);
  }, [file]);

  return url ? <img src={url} /> : null;
}
	

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

useObjectUrl — Создание и отмена, обработка

	
import { useObjectUrl } from '@reactuses/core';

function FilePreview({ file }: { file?: File }) {
  const url = useObjectUrl(file);
  return url ? <img src={url} /> : null;
}
	

В этом и вся суть. Подпись:

	
function useObjectUrl(object: Blob | MediaSource): string | undefined;
	

Передайте объект Blob или File его подтип или MediaSource, получите обратно URL объекта — или undefined до того, как источник будет существовать. Внутри это один useEffect: вызовите, URL.createObjectURL(), когда источник присутствует, и верните очистку, которая вызывает URL.revokeObjectURL(). Отличие от версии, написанной вручную, заключается не в поведении, а в том, что пара хранится в одном проверенном месте, а не переопределяется для каждого компонента.

Когда источник меняется — и когда он отключается

Отмена происходит в обоих важных случаях:

  • Источник меняется. Замените file свойство на другое File, и хук аннулирует старый URL перед созданием нового — накопление данных не происходит по мере того, как пользователь просматривает список вложений.
  • Компонент размонтируется. Закройте модальное окно предварительного просмотра, перейдите на другую страницу, удалите элемент из списка — запустится очистка, и URL-адрес будет освобожден. Ничего не останется ждать сборщика мусора, который не знает, что нужно его удалить.

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

Реальные примеры использования

  • Предварительный просмотр файлов. Отображение изображения, видео или PDF-файла, только что выбранного пользователем, перед подтверждением загрузки — стандартный вариант.
  • Ответы API для работы с BLOB-объектами. Конечная точка, возвращающая объект Blob (сгенерированный экспорт, подписанный ресурс, fetch().blob() результат), может быть преобразована в загружаемый файл <a href>или встроенный объект <img> без <video>записи на диск.
  • Экспорт в Canvas. canvas.toBlob() создает файл Blob его можно напрямую передать в Canvas useObjectUrl для предварительного просмотра или предложить загрузку того, что пользователь только что нарисовал или обрезал.
  • MediaSourceдля пользовательских видеоплееров. MediaSource Это объект Media Source Extensions,, лежащий в основе адаптивной потоковой передачи и видео с пользовательской буферизацией — вы формируете поток программно, а затем вам все равно потребуется URL-адрес для его передачи <video src>. useObjectUrl. Он принимает его напрямую, так же, как и Blob.

SSR-Safe by Construction

URL.createObjectURL на сервере этого нет — нет DOM, нет Blob URL-адресов для генерации. useObjectUrl Он никогда не обращается к URL API во время рендеринга на сервере; он возвращает значение undefined до тех пор, пока эффект не будет выполнен на клиенте, поэтому нет typeof window необходимости запоминать какие-либо ограничения и отлаживать сбои сервера. Если вы проверяете другой код API браузера, написанный вручную, на наличие аналогичной уязвимости, SSR-безопасные React Hooks охватывают общий шаблон.

Трио обработки файлов

useObjectUrl - это лишь один из небольшого набора механизмов, охватывающих весь жизненный цикл файла — выбор файла, перетаскивание файла и его предварительный просмотр:

  1. useFileDialog - Открывает встроенное средство выбора файлов и возвращает выбранный файл. FileList
  2. useDropZone - Превращает любой элемент в область для перетаскивания файлов.
  3. useObjectUrl - Преобразует полученный символ File/Blobв безопасный URL-адрес предварительного просмотра с автоматическим аннулированием.

Подключите useFileDialog или useDropZone result напрямую к useObjectUrl и на этапе предварительного просмотра не потребуется никакого кода для очистки. Полное пошаговое руководство, включая галерею из нескольких файлов, объединяющую все три варианта, находится в разделе «Обработка файлов в React».

Основные выводы

  • Основная проблема заключается в ошибке сопоставления. Каждому компоненту URL.createObjectURL() требуется соответствующее соответствие URL.revokeObjectURL() и именно повторное определение этого соответствия в каждом компоненте, отображающем предварительный просмотр файла, приводит к утечкам памяти.
  • useObjectUrl(object) принимает Blob (или File) или MediaSource и возвращает строку URL или undefined до того, как источник будет существовать.
  • Отмена происходит автоматически при обоих важных триггерах: изменении исходного кода и размонтировании компонента.
  • Рассматриваются не только предварительный просмотр изображений: ответы Blob API, canvas.toBlob() экспорт и MediaSource пользовательские видеоплееры.
  • Безопасно для SSR - возвращает данные undefined на сервер, не требуется URL доступ к API, не нужен защитный код.
  • Совместимость с useFileDialog функциями useDropZone выбора, перетаскивания и предварительного просмотра позволяет обойтись без ручного управления жизненным циклом продукта.

Получите его извне @reactuses/core и прекратите проверку компонентов на наличие пропущенных revokeObjectURL вызовов.

Помощь сайту
ЮMoney:
4100 1180 7209 833
Карта Сбербанк:
2202 2080 6183 7127

Возможно, вам будет интересно

Что нового в react-router v6?

Для тех, кто только начинает изучать React, сталкивается с проблемами организации роутов в приложении, т.к. в интернете много материалов по работе react-router более ранних версий. В данной статье будут описаны основная разница и новшества работы с react-router v6.

React useHooks. Часть 1 - полезные React хуки.

Хуки — это функции в React, которые позволяют Вам использовать состояние и другие функции React без написания классов. В этой статье мы будем рассматривать такие хуки, как useToggle, useFirestoreQuery, useMemoCompare, useAsync, useRequireAuth, useRouter, useAuth, useEventListener, useWhyDidYouUpdate, useDarkMode.

Лучшие практики ReactJS с TypeScript

ReactJS и TypeScript — это мощные технологии, которые можно объединить для создания надежных и типобезопасных приложений. В этом техническом документе изложены лучшие практики, которым нужно следовать при использовании ReactJS с TypeScript.

Руководство по внедрению Tailwind CSS в React JS

В этом руководстве мы расскажем, как настроить или добавить Tailwind CSS в приложение React JS. Мы также покажем Вам пример, как создать простой компонент с помощью фреймворка Tailwind CSS.