构建单页应用程序 (SPA) 的优势之一是页面间导航速度极快。不幸的是,我们组件的数据有时只有在我们导航到应用程序的特定部分之后才可用。我们可以通过将组件分解为两个部分来提升用户的感知性能:容器(在为空时显示骨架视图)和内容。如果我们延迟渲染内容组件,直到我们实际收到所需内容,那么我们就可以利用容器的骨架视图,从而提高感知加载时间!
让我们开始创建组件。
我们正在构建什么
我们将利用文章“使用 CSS 自定义属性构建骨架屏幕”中构建的骨架组件。

这是一篇很棒的文章,概述了如何创建骨架组件,并且 :empty
选择器的使用使我们能够巧妙地在组件内部使用 {this.props.children}
,以便在内容不可用时渲染骨架卡片。
查看 CodePen 上 Mathias Rechtzigel (@MathiasaurusRex) 的笔 React 16 — 骨架卡片 – 最终版。
创建我们的组件
我们将创建几个组件来帮助我们入门。
- 外部容器 (
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>
查看 CodePen 上 Mathias Rechtzigel (@MathiasaurusRex) 的笔 React 16 — 骨架卡片 – 卡片内容无状态。
使用三元运算符在加载状态后显示内容
现在我们有了 CardContainer
和 CardContent
组件,我们已将卡片拆分为创建骨架组件的必要部分。但是,如何在加载内容时在这两者之间切换呢?
这就是巧妙使用状态和三元运算符来救援的地方!
我们将在本节中执行三件事
- 创建一个最初设置为
false
的状态对象 - 更新我们的组件以使用三元运算符,以便在状态为
false
时不会渲染cardContent
组件 - 在我们收到信息后,将状态设置为我们对象的内容
我们希望将内容的默认状态设置为 false
。这隐藏了卡片内容,并允许 CSS :empty
选择器发挥其魔力。
this.state = {
cardContent: false
};
现在我们必须更新 CardContainer
的子元素以包含三元运算符。在我们的例子中,它查看 this.state.cardContent
以查看它是否解析为 true 或 false。如果为 true
,则执行冒号 (:
) 左侧的所有内容。相反,如果为 false
,则执行冒号右侧的所有内容。这非常有用,因为对象将解析为 true
,如果我们将初始状态设置为 false
,则我们的组件拥有实施骨架组件所需的所有条件!
让我们将所有内容组合到我们的主应用程序中。我们暂时不会担心 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>
成功!如您所见,由于 cardContent
的状态设置为 false
,因此卡片正在渲染为骨架组件。接下来,我们将创建一个函数,将 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
})
}
在此示例中,我们正在函数内部设置状态。我们还可以利用 React 的生命周期方法来填充组件的状态。根据我们的需求,我们将不得不查看要使用的方法。例如,如果我正在加载单个组件并希望从 API 获取内容,那么我们将使用 ComponentDidMount 生命周期方法。如文档所述,我们必须小心地以这种方式使用此生命周期方法,因为它可能会导致额外的渲染——但将初始状态设置为 false
应该可以防止这种情况发生。
查看 CodePen 上 Mathias Rechtzigel (@MathiasaurusRex) 的笔 React 16 — 骨架卡片 – 最终版。
列表中的第二张卡片已连接到设置 cardContent
状态的点击事件。一旦状态设置为内容的对象,卡片的骨架版本就会消失,并显示内容,确保用户不会看到 UI 闪烁(流感季节即将来临,因此我们不想让用户感染流感!)。
让我们回顾一下
我们涵盖了很多内容,所以让我们回顾一下我们做了什么。
- 我们创建了一个
CardContainer
。容器组件利用:empty
伪选择器,以便在为空时渲染组件的骨架视图。 - 我们创建了嵌套在
CardContainer
内部的CardContent
组件,我们将状态传递给它。 - 我们将
cardContent
的默认状态设置为false
- 我们使用三元运算符仅在我们收到内容并将其放入
cardContent
状态对象中时渲染内部内容组件。
就是这样!通过在 UI 渲染和接收填充内容的数据之间创建中间状态,可以提高感知性能。
您以自己的方式使用类似 BEM 的语法有点令人困惑:如果
card-copy
是您的块,则card-copy--title
应该是此块的修饰符(我期望它是根据 BEM 约定),但在您的情况下,它显然是块的元素,应该命名为card-copy__title
。再说一次,我并不是建议您必须这样编写您的类,但是当我在组合的类名中看到双破折号或下划线时,我立即期望看到 BEM。这只是令人困惑。我不喜欢这里使用三元运算符的想法。如果您要使用 Flow 或 Typescript 之类的静态类型解决方案,它会变得有点乱。相反,它可以为空——例如,默认值将是
null
而不是false
。应该也能正常工作。