停止使用加载微调器 开发 Skeleton 心智模型 添加 TypeScript 支持 使用 Artist 组件列表和占位符 奖励 TypeScript 实用程序 术语 带有 Suspense 的占位符 占位符文本 告诉我你的想法

2025-05-25

停止使用加载旋转器

建立Skeleton心智模型

添加 TypeScript 支持

使用Artist组件

带占位符的列表

额外的 TypeScript 实用程序

术语

带有 Suspense 的占位符

占位符文本

告诉我你的想法

骨架比旋转按钮更好。如果您要刷新数据或获取更多信息,请显示旋转按钮。但是,在没有数据的屏幕上,使用骨架可以减少空旷感。

如果你关注我的Twitter,你就会知道我有多喜欢骨架。我甚至在我的 React Native (+ Web) 动画库MotiSkeleton中添加了一个组件。

TLDR

不要这样做:

if (!artist) return <Spinner />

return <Artist artist={artist} />
Enter fullscreen mode Exit fullscreen mode

相反,让它Artist自己处理加载状态。

当涉及到项目列表时,情况会稍微复杂一些。但我会在最后讲解。

每当您构建一个异步接收数据的组件时,您都应该让它知道它的两个不同状态:加载和数据。


建立Skeleton心智模型

如果有一个要点,那就是:每个具有加载状态的组件都应该呈现自己的占位符。

我特别喜欢 Paco Coursey 的这条推文。

一旦你有了一个漂亮的<Skeleton />组件,你的工作似乎就完成了。

例如,使用Moti's Skeleton,你所要做的就是:

import { Skeleton } from '@motify/skeleton'

const Artist = ({ artist }) => {
    const loading = !artist

    return (
      <Skeleton show={loading}>
          <Text>{artist ? artist.name : 'Loading...'}</Text>
      </Skeleton>
    )
}
Enter fullscreen mode Exit fullscreen mode

看起来很容易。所以我们Skeleton只要组件处于加载状态就可以使用它了,对吧?

当然。但让我们更进一步,开发一个用于构建异步显示数据的可靠组件的思维模型。

我们希望组件明确知道是否应该显示占位符状态。幸好,TypeScript 让这一切变得简单。

添加 TypeScript 支持

让我们以我们的Artist组件为例,并在组件外部定义它的加载状态。

一个简单的实现可能看起来像这样:

type ArtistProps = {
  artist: ArtistSchema | null
  loading: boolean
}
Enter fullscreen mode Exit fullscreen mode

但这很糟糕。

我们的类型应该描述我们的 React 状态的形状。

然而,上面的代码让不可能的情况通过了类型检查器。

if (props.loading && props.artist) {
  // typescript won't fail here, but it should!
}
Enter fullscreen mode Exit fullscreen mode

让我们将代码更改为使用类型联合,并转换boolean为严格选项:

type ArtistProps =
  | {
      artist: ArtistSchema
      loading: false
    }
  | {
      artist?: never
      loading: true
    }

const Artist = (props) => {
  return (
    <Skeleton show={props.loading}>
      <Text>{!props.loading ? props.artist.name : 'Loading...'}</Text>
    </Skeleton>
  )
}
Enter fullscreen mode Exit fullscreen mode

请注意,ArtistProps使用loading: true|false而不是boolean

每当props.loading是 时true,TypeScript 就知道artist不存在。通过设置artist?: never,我们可以确保使用组件在加载时无法传递prop。artist

使用Artist组件

Artist从父级接收artistloadingprops。这个父级是什么样子的?

// this is our type from earlier
type ArtistProps =
  | {
      artist: ArtistSchema
      loading: false
    }
  | {
      artist?: never
      loading: true
    }

// and this is the parent component
const ArtistScreen = () => {
  const artist = useSWR('/artist')

  return (
    <Artist
      {...(artist.data
        ? { artist: artist.data, loading: false }
        : { loading: true })}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

很简单。现在我们的 有了两个互斥的状态Artist。加载时,显示骨架。加载未完成时,显示艺术家。

现在,我们将逻辑转移到 TypeScript,通过自动完成功能,我们获得了愉快的开发体验。

您可以在此处的视频中看到它的样子:

带占位符的列表

列表的原理与单个项目的原理类似。

然而,列表应该考虑 3 种状态:emptyloadingdata

const ArtistsList = () => {
  const artists = useSWR('/artists')

  // pseudo code
  const loading = !artists.data
  const empty = artists.data?.length === 0
  const data = !!artists.data
}
Enter fullscreen mode Exit fullscreen mode

有 3 种可能的情况:

  1. 尚未加载数据
  2. 数据中没有艺术家
  3. 数据中加载了超过零位艺术家

布局列表逻辑

const ArtistList = () => {
  const artists = useSWR('/artists')

  if (!artists.data) {
    // we still need to make this
    return <ArtistListPlaceholder />
  } else if (artists.data.length === 0) {
    // make this yourself
    return <Empty />
  }

  return artists.map(artist => (
    <Artist artist={artist} key={artist.id} loading={false} />
  )
}
Enter fullscreen mode Exit fullscreen mode

剩下唯一要做的事情就是制作ArtistListPlaceholder组件。

创造ArtistListPlaceholder

我们已经有一个Artist具有潜在加载状态的组件,因此我们需要做的就是创建一个Artist组件数组,然后传递loading={true}

const ArtistListPlaceholder = () => {
  // you can adjust this number to fit your UI
  const placeholders = new Array(4).fill('')

  return placeholders.map((_, index) => (
    <Artist
      // index is okay as the key here
      key={`skeleton-${index}`}
      loading
    />
  ))
}
Enter fullscreen mode Exit fullscreen mode

我们的列表的最终代码如下所示:

const ArtistListPlaceholder = () => { 
  const placeholders = new Array(4).fill('')

  return placeholders.map((_, index) => (
    <Artist 
      key={`skeleton-${index}`}
      loading
    />
  ))
}

const ArtistList = () => {
  const artists = useSWR('/artists')

  if (!artists.data) {
    return <ArtistListPlaceholder />
  } else if (artists.data.length === 0) {
    return <Empty />
  }

  return artists.map(artist => (
    <Artist artist={artist} key={artist.id} loading={false} />
  )
}
Enter fullscreen mode Exit fullscreen mode

我喜欢将占位符和列表组件放在同一个文件中。这样更容易维护。

结果是一个漂亮的骨架列表:

淡入淡出列表

在上面的视频中,我先淡出了占位符列表,然后再淡入数据。这要归功于 Moti 的AnimatePresence组件:

额外的 TypeScript 实用程序

由于我在许多组件上都使用了骨架,因此我制作了这种类型的实用程序来生成它们的道具:

type Never<T> = Partial<Record<keyof T, never>>

export type LoadingProps<PropsOnceLoaded> =
  | ({ loading: true } & Never<PropsOnceLoaded>)
  | ({ loading: false } & PropsOnceLoaded)
Enter fullscreen mode Exit fullscreen mode

这样,你就可以轻松制作如下组件:

type Props = LoadingProps<{ artist: ArtistSchema }>

const Artist = (props: Props) => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

术语

loading通常被用作一个包罗万象的术语,用来描述获取初始数据、刷新和获取更多信息。如果您愿意,可以将上述示例中的loadingprop 更改为。这属于个人偏好问题。我喜欢,但我可以确信是一个更好的名字。placeholderloadingplaceholder

但是,不要empty与 互换使用loading,因为empty意味着列表中已加载零个项目。

我经常交替使用“占位符”和“骨架”。可以把骨架想象成实现占位符状态的 UI。

带有 Suspense 的占位符

当谈到悬念时,构造组件可能会有点不同,因为后备 UI 位于组件之外。

很有可能,你会做这样的事情:

const ArtistWithData = () => {
  const artist = getArtist()

  return <Artist artist={artist} loading={false} />
}

const SuspendedArtist = () => {
  return (
    <Suspense fallback={<Artist loading />}>
      <ArtistWithData />
    </Suspense>
  )
}
Enter fullscreen mode Exit fullscreen mode

在 Suspense 成为数据获取的主流之前,我无法给出明确的结论,但我认为这种模式将会持续下去。我实际上很少使用 Suspense,所以如果你有其他关于布局占位符内容的想法,请告诉我。

占位符文本

这是我们的原始Artist组件:

const Artist = (props) => {
  return (
    <Skeleton show={props.loading}>
      <Text>{!props.loading ? props.artist.name : 'Loading...'}</Text>
    </Skeleton>
  )
}
Enter fullscreen mode Exit fullscreen mode

请注意,我写的是Loading...当我们处于加载状态时。

文本Loading...实际上永远不会显示给用户;相反,它仅用于设置骨架的宽度。

或者,您可以使用固定的,只要是width适用showtrue

<Skeleton width={80} show={props.loading}>
  <Text>{props.artist?.name}</Text>
</Skeleton>
Enter fullscreen mode Exit fullscreen mode

告诉我你的想法

就这些。更多内容请关注我的推特(Fernando Rojo) 。

文章来源:https://dev.to/nandotherojo/stop-using-loading-spinners-pkh
PREV
AWS 网络备忘单 - EIP、ENI、VPC 等
NEXT
如何将 Django 连接到 ReactJs。设置 Django 后端。设置 React 前端。将前端应用程序连接到 Django 后端。注意