发布于 2025-12-10 1 阅读
0

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

停止使用加载旋转器

建立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