Создание скелета компонента в React

Это вольный перевод статьи «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.

Создание нашего компонента

Мы создадим пару компонентов, которые помогут нам начать:

  1. Внешний контейнер (CardContainer).
  2. Внутренний контент (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 и тернарный оператор.

Мы сделаем три вещи:

  1. Создадим в state объект, который по умолчанию равен false.
  2. Добавим в наш компонент тернарный оператор, чтобы не отображать cardContent, пока объект равен false.
  3. Как только мы получим контент, запишем его в 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.

Итак, что же мы сделали?

  1. Создали компонент cardContainer. Этот компонент отображает скелет с помощью псевдокласса :empty, когда он пуст.
  2. Создали компонент cardContent, который вложен в cardContainer и которому мы передаём наши данные из state.
  3. Установили state для cardContent равный false.
  4. Использовали тернарный оператор, чтобы рендерить внутренний контент компонента только, когда получаем его и загружаем в state cardContent.

И вот и всё! Теперь кажется, что контент загружается быстрее.

Комментариев ещё нет. Оставьте первый!