关于 react-ui-patterns
为 React 界面开发提供状态处理模式,面向构建数据驱动组件的开发者。内容聚焦加载、错误、空状态和异步操作,让界面反馈更清晰。
主要功能
- 01避免已有数据时显示过期加载态
- 02提供可重试的错误反馈
- 03为异步操作禁用按钮
- 04为列表设计空状态
- 05按内容形状选择骨架屏或旋转指示器
- 06检查加载、错误与成功状态
适用场景
- 01处理查询刷新时的加载状态
- 02向用户呈现表单错误
- 03设计无搜索结果和空列表界面
- 04测试组件的各类数据状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ liuchiawei/ agent-skills --skill react-ui-patterns安装后在 Claude Code 中输入/react-ui-patterns确认已加载。
详细介绍
React UI 模式
核心原则
- 绝不展示过时的 UI - 仅在真正加载时才显示加载指示器
- 始终暴露错误 - 用户必须知道何时出错
- 乐观更新 - 让 UI 感觉即时响应
- 渐进式披露 - 内容可用时即展示
- 优雅降级 - 部分数据总比没有数据好
加载状态模式
黄金法则
仅当没有数据可显示时才显示加载指示器。
// CORRECT - Only show loading when no data exists
const { data, loading, error } = useGetItemsQuery();
if (error) return <ErrorState error={error} onRetry={refetch} />;
if (loading && !data) return <LoadingState />;
if (!data?.items.length) return <EmptyState />;
return <ItemList items={data.items} />;
// WRONG - Shows spinner even when we have cached data
if (loading) return <LoadingState />; // Flashes on refetch!
加载状态决策树
Is there an error?
→ Yes: Show error state with retry option
→ No: Continue
Is it loading AND we have no data?
→ Yes: Show loading indicator (spinner/skeleton)
→ No: Continue
Do we have data?
→ Yes, with items: Show the data
→ Yes, but empty: Show empty state
→ No: Show loading (fallback)
骨架屏 vs 加载指示器
| 使用骨架屏的场景 | 使用加载指示器的场景 |
|---|---|
| 已知内容形状 | 未知内容形状 |
| 列表/卡片布局 | 模态操作 |
| 初始页面加载 | 按钮提交 |
| 内容占位符 | 内联操作 |
错误处理模式
错误处理层级
1. Inline error (field-level) → Form validation errors
2. Toast notification → Recoverable errors, user can retry
3. Error banner → Page-level errors, data still partially usable
4. Full error screen → Unrecoverable, needs user action
始终显示错误
关键:绝不要静默吞掉错误。
// CORRECT - Error always surfaced to user
const [createItem, { loading }] = useCreateItemMutation({
onCompleted: () => {
toast.success({ title: 'Item created' });
},
onError: (error) => {
console.error('createItem failed:', error);
toast.error({ title: 'Failed to create item' });
},
});
// WRONG - Error silently caught, user has no idea
const [createItem] = useCreateItemMutation({
onError: (error) => {
console.error(error); // User sees nothing!
},
});
错误状态组件模式
interface ErrorStateProps {
error: Error;
onRetry?: () => void;
title?: string;
}
const ErrorState = ({ error, onRetry, title }: ErrorStateProps) => (
<div className="error-state">
<Icon name="exclamation-circle" />
<h3>{title ?? 'Something went wrong'}</h3>
<p>{error.message}</p>
{onRetry && (
<Button onClick={onRetry}>Try Again</Button>
)}
</div>
);
按钮状态模式
按钮加载状态
<Button
onClick={handleSubmit}
isLoading={isSubmitting}
disabled={!isValid || isSubmitting}
>
Submit
</Button>
操作期间禁用
关键:在异步操作期间始终禁用触发器。
// CORRECT - Button disabled while loading
<Button
disabled={isSubmitting}
isLoading={isSubmitting}
onClick={handleSubmit}
>
Submit
</Button>
// WRONG - User can tap multiple times
<Button onClick={handleSubmit}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</Button>
空状态
空状态要求
每个列表/集合都必须有空状态:
// WRONG - No empty state
return <FlatList data={items} />;
// CORRECT - Explicit empty state
return (
<FlatList
data={items}
ListEmptyComponent={<EmptyState />}
/>
);
上下文空状态
// Search with no results
<EmptyState
icon="search"
title="No results found"
description="Try different search terms"
/>
// List with no items yet
<EmptyState
icon="plus-circle"
title="No items yet"
description="Create your first item"
action={{ label: 'Create Item', onClick: handleCreate }}
/>
表单提交模式
const MyForm = () => {
const [submit, { loading }] = useSubmitMutation({
onCompleted: handleSuccess,
onError: handleError,
});
const handleSubmit = async () => {
if (!isValid) {
toast.error({ title: 'Please fix errors' });
return;
}
await submit({ variables: { input: values } });
};
return (
<form>
<Input
value={values.name}
onChange={handleChange('name')}
error={touched.name ? errors.name : undefined}
/>
<Button
type="submit"
onClick={handleSubmit}
disabled={!isValid || loading}
isLoading={loading}
>
Submit
</Button>
</form>
);
};
反模式
加载状态
// WRONG - Spinner when data exists (causes flash)
if (loading) return <Spinner />;
// CORRECT - Only show loading without data
if (loading && !data) return <Spinner />;
错误处理
// WRONG - Error swallowed
try {
await mutation();
} catch (e) {
console.log(e); // User has no idea!
}
// CORRECT - Error surfaced
onError: (error) => {
console.error('operation failed:', error);
toast.error({ title: 'Operation failed' });
}
按钮状态
// WRONG - Button not disabled during submission
<Button onClick={submit}>Submit</Button>
// CORRECT - Disabled and shows loading
<Button onClick={submit} disabled={loading} isLoading={loading}>
Submit
</Button>
检查清单
在完成任何 UI 组件之前:
UI 状态:
- 错误状态已处理并展示给用户
- 仅当不存在数据时才显示加载状态
- 为集合提供空状态
- 在异步操作期间禁用按钮
- 按钮在适当时显示加载指示器
数据与变更:
- 变更具有 onError 处理程序
- 所有用户操作都有反馈(toast/视觉)
与其他技能的集成
- graphql-schema:使用带有恰当错误处理的变更模式
- testing-patterns:测试所有 UI 状态(加载、错误、空、成功)
- formik-patterns:应用表单提交模式
评论
支持 Markdown