Это вольный перевод статьи «Building Skeleton Components with React» Mathias Rechtzigel. В примерах, приведённых в статье, используется БЭМ-подобный код, который по началу вводит в замешательство.
Одно из преимуществ одностраничных приложений (SPA) — это очень быстрый переход между страниц. К несчастью, контент в наших компонентах иногда бывает доступен только после того, как мы перешли на какую-то часть нашего приложения. Мы можем улучшить восприятие пользователем производительности приложения, разделив компонент на две части: на контейнер (который отображает скелет пока контента нет) и сам контент. Если мы отложим рендеринг контента до тех пор, пока он полностью не загрузится и отобразим скелет компонента, то мы уменьшим воспринимаемое время ожидания.
Давайте начнём создавать наш компонент.
Что мы делаем?
Мы внедрим скелет компонента, который был создан в статье «Создание скелета компонента на CSS-переменных».
Это отличная статья, в которой описывается как создать скелет компонента и использовать псевдокласс :empty
, чтобы хорошо умно использовать {this.props.children}
внутри наших компонентов для того, чтобы отображать скелет каждый раз, когда контент недоступен.
See the Pen React 16 — Skeleton Card — Final by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.
Создание нашего компонента
Мы создадим пару компонентов, которые помогут нам начать:
- Внешний контейнер (CardContainer).
- Внутренний контент (CardContent).
Сначала создадим наш CardContainer. Этот компонент-контейнер будет опираться на псевдокласс :empty
для отображения скелета, когда он пуст.
class CardContainer extends React.Component {
render() {
return (
<div className="card">
{this.props.children}
</div>
);
}
}
Затем мы создадим компонент CardContent, который будет вложен в CardContainer.
class CardContent extends React.Component {
render() {
return (
<div className="card--content">
<div className="card-content--top">
<div className="card-avatar">
<img
className="card-avatar--image"
src={this.props.avatarImage}
alt="" />
<span>{this.props.avatarName}</span>
</div>
</div>
<div className="card-content--bottom">
<div className="card-copy">
<h1 className="card-copy--title">{this.props.cardTitle}</h1>
<p className="card-copy--description">{this.props.cardDescription}</p>
</div>
<div className="card--info">
<span className="card-icon">
<span className="sr-only">Total views: </span>
{this.props.countViews}
</span>
<span className="card-icon">
<span className="sr-only">Total comments: </span>
{this.props.countComments}
</span>
</div>
</div>
</div>
);
}
}
Как вы видите, здесь есть пара мест, где отображаются пропсы (например, аватар, имя и ещё некоторая информация).
Сложим их вместе и получим целый компонент карточки.
<CardContainer>
<CardContent
avatarImage='path/to/avatar.jpg'
avatarName='FirstName LastName'
cardTitle='Title of card'
cardDescription='Description of card'
countComments='XX'
countViews='XX'
/>
</CardContainer>
See the Pen React 16 — Skeleton Card — Card Content No State by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.
Использование тернарного оператора для отображения контента
Теперь, когда у нас есть компоненты CardContainer и CardContent, мы разделили нашу карточку на необходимые части, чтобы создать скелет. Но как мы переключаемся, когда контент загружен?
Это то место, где нам помогут state и тернарный оператор.
Мы сделаем три вещи:
- Создадим в state объект, который по умолчанию равен false.
- Добавим в наш компонент тернарный оператор, чтобы не отображать cardContent, пока объект равен false.
- Как только мы получим контент, запишем его в state.
Мы хотим установить начальное значение false в state. Это скроет контент в карте и позволит :empty сделать магию.
this.state = {
cardContent: false
};
Теперь мы должны обновить детей CardContainer, чтобы включить тернарный оператор. В нашем случае, оператор смотрит на this.state.cardContent
, который возвращает true или false. Если true — он выполняет код до двоеточия (:), если false — после. Это классно, потому что объекты возвращают true и, установив начальное значение false, у нас будет всё, чтобы реализовать скелет!
Давайте соединим всё в нашем приложении. Мы пока не должны беспокоиться о state внутри CardContent. Мы свяжем его с кнопкой, чтобы сымитировать получение данных из API.
<CardContainer>
{this.state.cardContent
?
<CardContent
avatarImage={this.state.cardContent.card.avatarImage}
avatarName={this.state.cardContent.card.avatarName}
cardTitle={this.state.cardContent.card.cardTitle}
cardDescription={this.state.cardContent.card.cardDescription}
countComments={this.state.cardContent.card.countComments}
countViews={this.state.cardContent.card.countViews}/>
:
null
}
</CardContainer>
Бум! Как вы видите, карточка отображает скелет, пока state cardContent равен false. Далее мы создадим функцию, которая устанавливает state cardContent имитирует объект с данными карточки (dummyCardData):
populateCardContent = (event) => {
const dummyCardData = {
card: {
avatarImage: "https://gravatar.com/avatar/f382340e55fa164f1e3aef2739919078?s=80&d=https://codepen.io/assets/avatars/user-avatar-80x80-bdcd44a3bfb9a5fd01eb8b86f9e033fa1a9897c3a15b33adfc2649a002dab1b6.png",
avatarName: "Mathias Rechtzigel",
cardTitle: "Minneapolis",
cardDescription:"Winter is coming, and it will never leave",
countComments:"52",
countViews:"32"
}
}
const cardContent = dummyCardData
this.setState({
cardContent
})
}
В этом примере мы настраиваем state внутри функции. Мы также можем использовать методы жизненного цикла React для заполнения state компонента. Здесь нам надо выбрать подходящий к нашим требованиям метод. К примеру, если я загружаю один компонент и хочу получить контент через API, то я могу использовать метод ComponentDidMount. Как говорится в документации, мы должны использовать этот метод осторожно, чтобы не вызывать дополнительный рендеринг. Установка начального состояния false должна нам помочь.
See the Pen React 16 — Skeleton Card — Final by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.
Вторая карточка подключена к событию «click», который устанавливает state для cardContent. Когда это происходит, скелет карточки пропадает и отображается содержимое, гарантируя нам, что пользователь не увидит мелькание UI.
Итак, что же мы сделали?
- Создали компонент cardContainer. Этот компонент отображает скелет с помощью псевдокласса :empty, когда он пуст.
- Создали компонент cardContent, который вложен в cardContainer и которому мы передаём наши данные из state.
- Установили state для cardContent равный false.
- Использовали тернарный оператор, чтобы рендерить внутренний контент компонента только, когда получаем его и загружаем в state cardContent.
И вот и всё! Теперь кажется, что контент загружается быстрее.
Поделиться