使用 React 构建骨架组件

Avatar of Mathias Rechtzigel
Mathias Rechtzigel

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费信用额度!

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

让我们开始创建组件。

我们正在构建什么

我们将利用文章“使用 CSS 自定义属性构建骨架屏幕”中构建的骨架组件。

这是一篇很棒的文章,概述了如何创建骨架组件,并且 :empty 选择器的使用使我们能够巧妙地在组件内部使用 {this.props.children},以便在内容不可用时渲染骨架卡片。

查看 CodePen 上 Mathias Rechtzigel (@MathiasaurusRex) 的笔 React 16 — 骨架卡片 – 最终版

创建我们的组件

我们将创建几个组件来帮助我们入门。

  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>

查看 CodePen 上 Mathias Rechtzigel (@MathiasaurusRex) 的笔 React 16 — 骨架卡片 – 卡片内容无状态

使用三元运算符在加载状态后显示内容

现在我们有了 CardContainerCardContent 组件,我们已将卡片拆分为创建骨架组件的必要部分。但是,如何在加载内容时在这两者之间切换呢?

这就是巧妙使用状态和三元运算符来救援的地方!

我们将在本节中执行三件事

  1. 创建一个最初设置为 false 的状态对象
  2. 更新我们的组件以使用三元运算符,以便在状态为 false 时不会渲染 cardContent 组件
  3. 在我们收到信息后,将状态设置为我们对象的内容

我们希望将内容的默认状态设置为 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 闪烁(流感季节即将来临,因此我们不想让用户感染流感!)。

让我们回顾一下

我们涵盖了很多内容,所以让我们回顾一下我们做了什么。

  1. 我们创建了一个 CardContainer。容器组件利用 :empty 伪选择器,以便在为空时渲染组件的骨架视图。
  2. 我们创建了嵌套在 CardContainer 内部的 CardContent 组件,我们将状态传递给它。
  3. 我们将 cardContent 的默认状态设置为 false
  4. 我们使用三元运算符仅在我们收到内容并将其放入 cardContent 状态对象中时渲染内部内容组件。

就是这样!通过在 UI 渲染和接收填充内容的数据之间创建中间状态,可以提高感知性能。