mcpField
R
react-ui-patterns

react-ui-patterns

github.com/sickn33开发48.7k1.5k 次安装今天更新
代码仓库

关于 react-ui-patterns

react-ui-patterns 是用于构建 React 界面的技能,指导如何统一处理加载、错误、空状态和提交状态。它侧重异步交互反馈,避免界面显示过时或误导性内容。

主要功能

  1. 01按数据状态处理加载
  2. 02明确展示错误
  3. 03乐观更新指导
  4. 04空状态模式
  5. 05异步按钮与表单模式
  6. 06渐进式展示内容

适用场景

  1. 01设计查询的加载与错误状态
  2. 02构建带有变更反馈的表单
  3. 03检查组件是否缺少空状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill react-ui-patterns

安装后在 Claude Code 中输入/react-ui-patterns确认已加载。

详细介绍

React UI 模式

核心原则

  1. 绝不显示过期的 UI - 仅在真正加载时才显示加载指示器
  2. 始终暴露错误 - 用户必须知道何时出错
  3. 乐观更新 - 让 UI 感觉即时响应
  4. 渐进式披露 - 内容可用时即时展示
  5. 优雅降级 - 部分数据总比没有数据好

加载状态模式

黄金法则

仅当没有数据可显示时才显示加载指示器。

// CORRECT - Only show loading when no data exists
const { data, loading, error, refetch } = 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>Unable to load this content. Try again if the problem persists.</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 (event: React.FormEvent) => {
    event.preventDefault();
    if (!isValid) {
      toast.error({ title: 'Please fix errors' });
      return;
    }
    await submit({ variables: { input: values } });
  };

  return (
    <form onSubmit={handleSubmit}>
      <Input
        value={values.name}
        onChange={handleChange('name')}
        error={touched.name ? errors.name : undefined}
      />
      <Button
        type="submit"
        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