Как работать с контекстом в React?
2 года назад·5 мин. на чтение
Контекст позволяет родительскому компоненту сделать некоторую информацию доступной для любого компонента в дереве под ним — независимо от того, насколько он глубок — без явной передачи ее через пропсы.
Обычно вы передаете информацию от родительского компонента дочернему компоненту через пропс. Но передача пропса может стать многословной и неудобной, если вам придется передавать их через множество компонентов посередине или если многим компонентам вашего приложения требуется одна и та же информация.
Допустим, вы хотите, чтобы несколько заголовков в одном разделе всегда имели одинаковый размер:
Проблема с передачей пропсов в React
Передача пропсов - отличный способ явно передать данные через UI дерево компонентам, которые его используют. Но передача пропса может стать многословным и неудобным, когда вам нужно передать какой-то проп глубоко через дерево, или если многим компонентам нужен один и тот же проп. Ближайший общий предок может быть далек от компонентов, которые нуждаются в данных, и поднятие состояния вверх может привести к ситуации, иногда называемой “prop drilling”. Было бы здорово, просто «телепортировать» данные к компонентам дерева, которые в них нуждаются.Контекст: альтернатива передаче пропсов
Контекст позволяет родительскому компоненту предоставлять данные всему дереву под ним. Есть много применений для контекста. Вот один из примеров. Рассмотрим компонентHeading
, который принимает level
для своего размера:
// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Heading level={2}>Heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={5}>Sub-sub-sub-heading</Heading> <Heading level={6}>Sub-sub-sub-sub-heading</Heading> </Section> ); }
// Section.js export default function Section({ children }) { return ( <section className="section"> {children} </section> ); }
// Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return <h1>{children}</h1>; case 2: return <h2>{children}</h2>; case 3: return <h3>{children}</h3>; case 4: return <h4>{children}</h4>; case 5: return <h5>{children}</h5>; case 6: return <h6>{children}</h6>; default: throw Error('Unknown level: ' + level); } }
В настоящее время вы передаете проп// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Section> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Section> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Section> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> </Section> </Section> </Section> </Section> ); }
level
каждому <Heading>
отдельно:
Было бы неплохо, если бы вы могли передать проп<Section> <Heading level={3}>About</Heading> <Heading level={3}>Photos</Heading> <Heading level={3}>Videos</Heading> </Section>
level
в <Section>
и удалить его из <Heading>
. Таким образом, вы можете добиться того, чтобы все заголовки в одном разделе имели одинаковый размер:
Но как<Section level={3}> <Heading>About</Heading> <Heading>Photos</Heading> <Heading>Videos</Heading> </Section>
<Heading>
может узнать уровень своего ближайшего <Section>
? Для этого дочернему компоненту потребуется какой-то способ «попросить» данные откуда-то сверху на дереве.
Вы не можете сделать это только с пропсами. Именно здесь в игру вступает контекст. Вы можете сделать это в три шага:
- Создайте контекст. (Вы можете назвать его
LevelContext
, так как он предназначен для уровня заголовка) - Используйте этот контекст из компонента, которому требуются данные. (
Heading
будет использоватьLevelContext
) - Укажите этот контекст из компонента, задающего данные. (
Section
будет содержатьLevelContext
.)
Шаг 1: Создайте контекст
Во-первых, вам нужно создать контекст. Вам нужно будет экспортировать его из файла, чтобы ваши компоненты могли использовать его:Единственным аргументом// LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1);
createContext
является значение по умолчанию. Здесь 1
относится к самому большому уровню заголовка, но вы можете передать любое значение (даже объект). Вы увидите значение значения по умолчанию на следующем шаге.
Шаг 2: Используйте контекст
Импортируйте хукuseContext
из React и импортируйте контекст, созданный на первом шаге:
Как вы помните компонент// Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js';
Heading
считывает level
из пропса:
Вместо этого удалите пропexport default function Heading({ level, children }) { // ... }
level
и прочитайте значение из только что импортированного контекста LevelContext
:
export default function Heading({ children }) { const level = useContext(LevelContext); // ... }
useContext
— это хук. Так же, как useState
и useReducer
, вы можете вызвать хук только на верхнем уровне компонента React. useContext
сообщает React, что компонент Heading
хочет прочитать LevelContext
.
Теперь, когда компонент Heading
не имеет пропса level
, вам больше не нужно передавать проп level
в Heading
в вашем JSX.
Обновите JSX так, чтобы его получал Section
:
Обратите внимание, что этот пример еще не совсем работает. Все заголовки имеют одинаковый размер, потому что хоть мы и вызвали контекст, но еще не предоставили его. React не знает, где его взять. Если контекст не указан, React будет использовать значение по умолчанию, указанное на предыдущем шаге. В этом примере в качестве аргумента<Section level={4}> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> </Section>
createContext
мы передали 1
, поэтому useContext(LevelContext)
возвращает 1
, установив для всех этих заголовков <h1>
. Давайте исправим эту проблему, предоставив каждому Section
свой собственный контекст.
Шаг 3: Предоставьте контекст
КомпонентSection
в настоящее время рендерит дочерние компоненты. Оберните их поставщиком контекста (context provider), чтобы предоставить им LevelContext
:
Это говорит React: «Если какой-либо компонент внутри этогоimport { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return ( <section className="section"> <LevelContext.Provider value={level}> {children} </LevelContext.Provider> </section> ); }
<Section>
просит LevelContext
, дайте им этот level
». Компонент будет использовать значение ближайшего <LevelContext.Provider>
в UI дереве над ним.
Этот код будет работать так же как и в самой первой реализации. Но вам не нужно было передавать проп level
каждому компоненту Heading
. Вместо этого он «выясняет» свой уровень заголовка, спрашивая ближайший Section
выше:
- Вы передаете проп
level
в<Section>
. Section
оборачивает свои дочерние элементы в<LevelContext.Provider value={level}>
.Heading
запрашивает ближайшее значениеLevelContext
выше сuseContext(LevelContext)
.
Типичные варианты использования контекста
- Темы: Если ваше приложение позволяет пользователю изменять его внешний вид (например, в темном режиме), вы можете поместить поставщика контекста в верхнюю часть приложения и использовать этот контекст в компонентах, которые должны настроить свой визуальный вид.
- Текущий пользователь приложения: Многим компонентам может потребоваться знать текущего вошедшего в систему пользователя. Помещение его в контекст позволяет удобно читать его в любом месте дерева. Некоторые приложения также позволяют управлять несколькими учетными записями одновременно (например, оставлять комментарий как другой пользователь). В этих случаях может быть удобно обернуть часть пользовательского интерфейса во вложенного поставщика с другим значением текущего счета.
- Маршрутизация: Большинство решений маршрутизации используют внутренний контекст для хранения текущего маршрута. Таким образом, каждая ссылка «знает», активна она или нет. Если вы создаете свой собственный маршрутизатор, вы тоже можете это сделать.
- Управление состоянием: По мере роста вашего приложения вы можете получить большое состояние ближе к верхней части вашего приложения. Многие отдаленные компоненты ниже могут захотеть изменить его. Обычно используется редьюсер вместе с контекстом для управления сложным состоянием и передачи его удаленным компонентам без особых хлопот.
Итоги
- Контекст позволяет компоненту предоставлять некоторую информацию всему дереву под ним.
-
Чтобы передать контекст:
- Создайте и экспортируйте его с
export const MyContext = createContext(defaultValue)
. - Передайте его в хук
useContext(MyContext)
чтобы прочитать его в любом дочернем компоненте, независимо от глубины. - Оберните дочерние комоненты в
<MyContext.Provider value={...}>
для представления контекста из родительского компонента.
- Создайте и экспортируйте его с
- Контекст проходит через любые компоненты посередине.
- Контекст позволяет писать компоненты, которые «адаптируются к окружающей среде».
-
Прежде чем использовать контекст, попробуйте передать проп или передать JSX как
children
.
Как ускорить сайт с помощью ленивой загрузки изображений
2 года назад·1 мин. на чтение
Ленивая загрузка изображений — один из самых простых способов ускорить загрузку сайта, поскольку для самой простой реализации ленивой загрузки требуется всего одна строка кода.
Есть несколько продвинутых методов, которые вы можете использовать, чтобы ваша отложенная загрузка выглядела так же, как на изображении ниже, с размытием и плавным переходом от размытого к полному изображению. В этой статье рассмотрим все, что вам нужно знать о ленивой загрузке, а также о том, как создать этот продвинутый эффект ленивой загрузки.
Следующим шагом является создание
Это даст нам эффект, который мы ищем. Эффект размытия, который мы получаем автоматически, связан с тем, что сверхмаленькое изображение автоматически увеличивается браузером. Если вы хотите добавить больше размытия, вы всегда можете использовать свойство CSS filter, чтобы добавить фильтр к
Теперь единственное, что осталось сделать, это показать основное изображение после его загрузки. Это немного сложнее, чем остальная часть кода, который мы написали до сих пор, поскольку требует от нас использования JavaScript, но все же довольно просто. Нам просто нужно добавить прослушиватель событий к изображению, который будет срабатывать после загрузки изображения.
Здесь много кода, поэтому разберем его шаг за шагом. В коде JavaScript мы выбираем
Что такое ленивая загрузка?
Ленивая загрузка (отложенная загрузка) — это метод, используемый для отсрочки загрузки контента до тех пор, пока он не понадобится. В случае изображений это означает, что изображение не будет загружено до тех пор, пока пользователь не прокрутит до точки, где изображение будет видно на экране. Это отличный способ ускорить работу вашего сайта, поскольку вы загружаете только те изображения, которые пользователь действительно увидит. Это особенно полезно для сайтов с большим количеством изображений, поскольку вы можете сэкономить много пропускной способности, загружая только те изображения, которые пользователь действительно увидит. Если у вас высокая скорость интернета или вы просматриваете сайты только с небольшими, хорошо оптимизированными изображениями, вы можете не увидеть преимущества отложенной загрузки изображений, поскольку вы можете загрузить все изображения почти мгновенно. Но для всех остальных ленивая загрузка изображений меняет играет важную роль. Это касается не только людей со сверхмедленным интернет-соединением. Изображения являются одним из, если не самым большим по размеру контентом, который загрузит ваш пользователь, поэтому, даже если у него быстрое подключение к Интернету, ленивая загрузка изображений все равно может иметь огромное значение для времени загрузки вашего сайта.Базовая реализация ленивой загрузки
Как я уже упоминал в начале этой статьи, ленивая загрузка изображений так же проста, как добавление одного атрибута к тегу изображения. Атрибутуloading
можно присвоить значение lazy
, чтобы включить отложенную загрузку изображения. Браузер автоматически определит, когда загружать изображение, в зависимости от того, насколько близко изображение находится на экране.
Самым большим недостатком этой базовой отложенной загрузки является то, что пользователь увидит пустое место, где должно быть изображение, пока изображение не будет загружено. Это не идеальный пользовательский опыт, поэтому оставшаяся часть этой статьи покажет вам, как воспользоваться отложенной загрузкой, чтобы показать размытое изображение-заполнитель до тех пор, пока не будет загружено полное изображение.<img src="image.jpg" loading="lazy" />
Продвинутая отложенная загрузка
Размытые изображения-заполнители отображаются до тех пор, пока не будет загружено полное изображение, и являются первым шагом к созданию этого расширенного эффекта отложенной загрузки. Чтобы создать размытое изображение-заполнитель, вам просто нужно создать версию изображения со сверхнизким разрешением. Есть много способов сделать это, например, использовать такой сервис, как BlurHash, вручную изменить размер изображения в таком инструменте, как Figma, или автоматически с помощью такого инструмента, как ffmpeg. Мы будем использовать ffmpeg для создания изображений-заполнителей для этой статьи, поскольку это наиболее гибкий вариант, который можно легко автоматизировать. Все, что нужно сделать, это запустить приведенный ниже код в командной строке в каталоге, содержащем изображение, для которого требуется сгенерировать изображение-заполнитель.При этом будет сгенерировано изображение шириной 20 пикселей, а высота будет автоматически рассчитана для сохранения пропорций исходного изображения. Вы можете изменить ширину на любую другую, но по наблюдениям, 20 пикселей хорошо подходят для большинства изображений и достаточно малы, чтобы загружаться почти мгновенно даже при медленном интернет-соединении. Изображения-заполнители будут примерно по 1КБ каждое.ffmpeg -i imageName.jpg -vf scale=20:-1 imageName-small.jpg
div
и установка фонового изображения этого div
на наше супер маленькое изображение. Это будет изображение-заполнитель, которое будет отображаться до тех пор, пока не будет загружено полное изображение. Наш код будет выглядеть примерно так.
Этот<div class="blurred-img"></div> .blurred-img { background-image: url(imageName-small.jpg); background-repeat: no-repeat; background-size: cover; }
div
с blurred-img
имеет размер в зависимости от размера содержимого в нем. Однако мы можем легко исправить это, добавив img
в наш div
и убедившись, что он скрыт по умолчанию, чтобы мы никогда не видели его в наполовину загруженном состоянии.
<div class="blurred-img"> <img src="imageName.jpg" loading="lazy" /> </div>
.blurred-img img { opacity: 0; }
blurred-img
.
Вы даже можете сделать еще один шаг вперед, добавив пульсирующую анимацию к изображению-заполнителю. Это сделает еще более очевидным, что изображение загружается..blurred-img { filter: blur(10px); }
.blurred-img::before { content: ""; position: absolute; inset: 0; opacity: 0; animation: pulse 2.5s infinite; background-color: white; } @keyframes pulse { 0% { opacity: 0; } 50% { opacity: 0.1; } 100% { opacity: 0; } }
<div class="blurred-img"> <img src="imageName.jpg" loading="lazy" /> </div>
const blurredImageDiv = document.querySelector(".blurred-image") const img = blurredImageDiv.querySelector("img") function loaded() { blurredImageDiv.classList.add("loaded") } if (img.complete) { loaded() } else { img.addEventListener("load", loaded) } .blurred-img { background-repeat: no-repeat; background-size: cover; }
.blurred-img::before { content: ""; position: absolute; inset: 0; opacity: 0; animation: pulse 2.5s infinite; background-color: var(--text-color); } @keyframes pulse { 0% { opacity: 0; } 50% { opacity: 0.1; } 100% { opacity: 0; } } .blurred-img.loaded::before { animation: none; content: none; } .blurred-img img { opacity: 0; transition: opacity 250ms ease-in-out; } .blurred-img.loaded img { opacity: 1; }
blurred-img
а затем выбираем img
в этом div
. Затем мы проверяем свойство complete
у img
, чтобы увидеть, загрузился ли он еще. Если это так, это означает, что изображение уже загружено, поэтому мы можем просто вызвать функцию loaded
. Однако, если это условие ложно, нам нужно добавить прослушиватель событий в img
, который будет срабатывать после загрузки изображения, а затем вызывать loaded
. loaded
просто добавляет класс loaded
в blurred-img
.
В CSS у нас есть несколько изменений в коде. Сначала мы удалили animation
/content
из элемента blurred-img::before
. Это остановит пульсирующую анимацию после загрузки изображения. Мы также добавили transition
к элементу img
, чтобы он плавно исчезал при добавлении loaded
класса в div
blurred-img.img
Наконец, мы изменяем непрозрачность img
на 1
, чтобы она была видна при загрузке.