停止使用加载旋转器
建立Skeleton
心智模型
添加 TypeScript 支持
使用Artist
组件
带占位符的列表
额外的 TypeScript 实用程序
术语
带有 Suspense 的占位符
占位符文本
告诉我你的想法
骨架比旋转按钮更好。如果您要刷新数据或获取更多信息,请显示旋转按钮。但是,在没有数据的屏幕上,使用骨架可以减少空旷感。
如果你关注我的Twitter,你就会知道我有多喜欢骨架。我甚至在我的 React Native (+ Web) 动画库MotiSkeleton
中添加了一个组件。
TLDR
不要这样做:
if (!artist) return <Spinner />
return <Artist artist={artist} />
相反,让它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>
)
}
看起来很容易。所以我们Skeleton
只要组件处于加载状态就可以使用它了,对吧?
当然。但让我们更进一步,开发一个用于构建异步显示数据的可靠组件的思维模型。
我们希望组件明确知道是否应该显示占位符状态。幸好,TypeScript 让这一切变得简单。
添加 TypeScript 支持
让我们以我们的Artist
组件为例,并在组件外部定义它的加载状态。
一个简单的实现可能看起来像这样:
type ArtistProps = {
artist: ArtistSchema | null
loading: boolean
}
但这很糟糕。
我们的类型应该描述我们的 React 状态的形状。
然而,上面的代码让不可能的情况通过了类型检查器。
if (props.loading && props.artist) {
// typescript won't fail here, but it should!
}
让我们将代码更改为使用类型联合,并转换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>
)
}
请注意,ArtistProps
使用loading: true|false
而不是boolean
。
每当props.loading
是 时true
,TypeScript 就知道artist
不存在。通过设置artist?: never
,我们可以确保使用组件在加载时无法传递prop。artist
使用Artist
组件
Artist
从父级接收artist
和loading
props。这个父级是什么样子的?
// 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 })}
/>
)
}
很简单。现在我们的 有了两个互斥的状态Artist
。加载时,显示骨架。加载未完成时,显示艺术家。
现在,我们将逻辑转移到 TypeScript,通过自动完成功能,我们获得了愉快的开发体验。
您可以在此处的视频中看到它的样子:
带占位符的列表
列表的原理与单个项目的原理类似。
然而,列表应该考虑 3 种状态:empty
、loading
和data
。
const ArtistsList = () => {
const artists = useSWR('/artists')
// pseudo code
const loading = !artists.data
const empty = artists.data?.length === 0
const data = !!artists.data
}
有 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} />
)
}
剩下唯一要做的事情就是制作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
/>
))
}
我们的列表的最终代码如下所示:
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} />
)
}
我喜欢将占位符和列表组件放在同一个文件中。这样更容易维护。
结果是一个漂亮的骨架列表:
淡入淡出列表
在上面的视频中,我先淡出了占位符列表,然后再淡入数据。这要归功于 Moti 的AnimatePresence
组件:
额外的 TypeScript 实用程序
由于我在许多组件上都使用了骨架,因此我制作了这种类型的实用程序来生成它们的道具:
type Never<T> = Partial<Record<keyof T, never>>
export type LoadingProps<PropsOnceLoaded> =
| ({ loading: true } & Never<PropsOnceLoaded>)
| ({ loading: false } & PropsOnceLoaded)
这样,你就可以轻松制作如下组件:
type Props = LoadingProps<{ artist: ArtistSchema }>
const Artist = (props: Props) => {
// ...
}
术语
loading
通常被用作一个包罗万象的术语,用来描述获取初始数据、刷新和获取更多信息。如果您愿意,可以将上述示例中的loading
prop 更改为。这属于个人偏好问题。我喜欢,但我可以确信是一个更好的名字。placeholder
loading
placeholder
但是,不要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>
)
}
在 Suspense 成为数据获取的主流之前,我无法给出明确的结论,但我认为这种模式将会持续下去。我实际上很少使用 Suspense,所以如果你有其他关于布局占位符内容的想法,请告诉我。
占位符文本
这是我们的原始Artist
组件:
const Artist = (props) => {
return (
<Skeleton show={props.loading}>
<Text>{!props.loading ? props.artist.name : 'Loading...'}</Text>
</Skeleton>
)
}
请注意,我写的是Loading...
当我们处于加载状态时。
文本Loading...
实际上永远不会显示给用户;相反,它仅用于设置骨架的宽度。
或者,您可以使用固定的,只要是就width
适用。show
true
<Skeleton width={80} show={props.loading}>
<Text>{props.artist?.name}</Text>
</Skeleton>
告诉我你的想法
就这些。更多内容请关注我的推特(Fernando Rojo) 。
文章来源:https://dev.to/nandotherojo/stop-using-loading-spinners-pkh