关于 web-framework-react
面向 React 开发者的组件与 Hook 模式技能,重点涵盖 React 19。内容包括表单、ref、乐观更新、错误边界,以及适用于浏览器和服务端渲染的样式无关组件 API。
主要功能
- 01React 19 将 ref 作为属性及清理模式
- 02使用 useActionState 和 useFormStatus 管理表单状态
- 03乐观更新与 Promise 读取
- 04可复用组件、变体与自定义 Hook
- 05错误边界与运行时陷阱
适用场景
- 01将组件从 forwardRef 改为 ref 属性
- 02构建带等待和错误状态的表单
- 03添加乐观 UI 更新与可重试错误边界
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-framework-react安装后在 Claude Code 中输入/web-framework-react确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/core.mdhooks.mdreact-19-hooks.md详细介绍
React 模式
快速指南: React 19 确定了此技能所采用的四项做法——
ref是常规属性,forwardRef已无必要;表单提交状态来自useActionState;useFormStatus只能从子组件读取外层表单的状态;ref 回调可以返回清理函数。组件保持与样式无关:公开className,并通过data-*属性表示变体。
详细资源:
- examples/core.md — 组件结构、变体属性、事件处理器、可访问控件
- examples/hooks.md — usePagination、useDebounce、useLocalStorage
- examples/react-19-hooks.md — useActionState、useFormStatus、useOptimistic、use()、ref 清理
- reference.md — 决策树、附带修正代码的反模式、组件和 Hook 检查清单
适用路径
- 在浏览器中渲染的 React — 以下所有模式均可按原样使用;表单提交通过 examples/react-19-hooks.md 中的
useActionState完成。 - 由服务端框架渲染的 React — 提交和数据加载应使用该框架自己的服务端原语,因此跳过模式 6,组件、Hook、ref 和错误边界模式保持不变。
<critical_requirements>
编写 React 代码前
将 ref 作为常规属性传递。 React 19 无需 forwardRef 即可转发它,因此可以移除包装层和手动设置的 displayName。
每个可复用组件都应公开 className。 对于不了解此组件的使用者而言,这是唯一可用于设置组件样式的接口。
从在 <form> 内渲染的组件中调用 useFormStatus。 如果在渲染表单的组件中调用,它将永远返回 pending: false,且不会提示错误。
当表单需要待处理或错误状态时,优先使用 useActionState。 它会提供这两种状态,而且在水合之前,<form action={...}> 也能正常工作。
</critical_requirements>
自动检测: React 19、useActionState、useFormStatus、useOptimistic、use()、ref 作为属性、ref 清理、forwardRef 迁移、React.ComponentProps、getDerivedStateFromError、componentDidCatch、useCallback、自定义 Hook
适用范围:
- 组件属性、组合和变体 API
- 将
forwardRef组件迁移为 ref 作为属性 - 使用 React 19 Hook 处理表单提交、乐观更新和 Promise 读取
- 用于复用有状态逻辑的自定义 Hook
- 支持重试的错误边界
其他部分负责:
- 样式——组件接收
className,并通过data-*公开变体;具体采用哪种 CSS 方案不在此技能的职责范围内 - 生命周期长于组件的客户端状态,以及具有缓存和失效机制的服务端数据——组件通过属性接收两者,不必了解其来源
- 路由,以及路由加载的数据
- 网络请求的测试替身
核心模式
模式 1:组件结构
可复用组件会展开其元素自身的属性,直接接收 ref,并通过 data-* 属性表示变体,以便任何样式层都能选择这些属性。
export type ButtonProps = React.ComponentProps<"button"> & {
variant?: "default" | "ghost" | "link";
size?: "default" | "large" | "icon";
ref?: React.Ref<HTMLButtonElement>;
};
export function Button({ variant = "default", size = "default", className, ref, ...props }: ButtonProps) {
return <button className={className} data-variant={variant} data-size={size} ref={ref} {...props} />;
}
完整代码:examples/core.md
模式 2:带类型的变体属性
当组件有两个或更多视觉维度时,为其定义变体属性。只有一种视觉样式的组件只需接收 className,不需要其他属性——只有一个成员的联合类型只是没有第二种情况的抽象。
export type AlertVariant = "info" | "warning" | "error" | "success";
export function Alert({ variant = "info", className, ref, ...props }: AlertProps) {
return <div ref={ref} className={className} data-variant={variant} {...props} />;
}
完整代码:examples/core.md
模式 3:事件处理器命名
handle 前缀用于内部处理器(handleNameChange),on 前缀用于回调属性(onSelect);每个处理器都要为其事件指定类型——例如 FormEvent<HTMLFormElement>、ChangeEvent<HTMLInputElement>——这样错误的字段访问就会在编译时失败。
完整代码:examples/core.md
模式 4:自定义 Hook
调用 Hook 但不渲染任何内容的逻辑,应使用以 use 为前缀的 Hook,而不是组件:分页状态、防抖值、持久化偏好设置。
完整代码:examples/hooks.md
模式 5:带重试功能的错误边界
错误边界会捕获渲染错误并返回一个重置函数,因此暂时性故障只会影响一个区域,而不是整个页面。应在每个功能区域周围放置一个错误边界,而不只是放在根节点。
interface Props {
children: ReactNode;
fallback?: (error: Error, reset: () => void) => ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
错误边界必须是类,因为 getDerivedStateFromError 没有对应的 Hook 形式。错误边界的放置、恢复和回退界面设计是独立的主题,应在本技能之外处理。
模式 6:使用 useActionState 提交表单
这个 Hook 会返回 action 的上次结果、传给 <form action={...}> 的 action,以及一个待处理标记——替代三次 useState 调用及其重置逻辑。
async function updateProfile(prevState: string | null, formData: FormData) {
try {
await saveProfile({ name: formData.get("name") as string });
return null;
} catch {
return "Failed to save profile";
}
}
const [error, submitAction, isPending] = useActionState(updateProfile, null);
完整代码:examples/react-19-hooks.md
模式 7:使用 useFormStatus 处理提交按钮
提交按钮会自行读取所属表单的待处理状态,因此一个按钮组件就能适用于所有表单,无须表单向下传递该标记。
import { useFormStatus } from "react-dom";
function SubmitButton({ children }: { children: React.ReactNode }) {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} aria-busy={pending}>
{pending ? "Submitting..." : children}
</button>
);
}
完整代码:examples/react-19-hooks.md
模式 8:使用 useOptimistic 提供即时反馈
立即渲染预期结果;如果请求失败,React 会将其恢复。setter 会在 startTransition 内调用。
const [optimisticItems, addOptimistic] = useOptimistic(
items,
(state, update: Item) => [...state, { ...update, pending: true }],
);
startTransition(async () => {
addOptimistic(newItem);
await saveItem(newItem);
});
完整代码:examples/react-19-hooks.md
模式 9:使用 use() 读取 Promise 和 context
use() 会在渲染期间读取 Promise 或 context;与 useContext 不同,它可以在提前返回之后调用。Promise 会暂停渲染,直到其解析,因此调用方必须位于 <Suspense> 下方;若 Promise 被拒绝,则会由错误边界捕获。
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise);
return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
<Suspense fallback={<p>Loading...</p>}>
<Comments commentsPromise={fetchComments()} />
</Suspense>
完整代码:examples/react-19-hooks.md
模式 10:Ref 回调清理
Ref 回调可以返回一个清理函数,该函数会在卸载时运行——替代用于 DOM 设置的 useRef + useEffect 组合。
<video
ref={(video) => {
if (!video) return;
video.play();
return () => {
video.pause();
video.currentTime = 0;
};
}}
src={src}
/>
完整代码:examples/react-19-hooks.md
<red_flags>
注意事项
运行时会出错:
- 在渲染
<form>的组件中调用useFormStatus——pending会一直为 false,按钮也永远不会被禁用;应在子组件中调用 - 在
try/catch中调用use()——它会抛出异常以暂停渲染,因此 catch 会吞掉暂停;应改为用错误边界包裹调用方 - 在 transition 或表单 action 之外调用
useOptimisticsetter——React 会拒绝该调用,因此乐观值永远不会生效 - 在 SSR 期间的渲染中读取浏览器 API——使用
typeof window !== "undefined"进行保护,或将读取操作移至 effect 中 - 依赖错误边界捕获事件处理程序、异步操作或 SSR 错误——它只捕获渲染错误,因此这些路径需要各自的
try/catch
行为可能令人意外:
- 在 React 19 中,Ref 回调返回清理函数以外的任何内容都会导致类型错误,而且卸载时不再会以
null调用该回调 useOptimistic会自行在失败时恢复,因此手动回滚会与之冲突- 对传给未使用 memo 的子组件的处理程序使用
useCallback,会产生一次分配,却没有任何收益;无论如何,React Compiler 都会让这个包装变得多余 forwardRef仍能编译,因此不会有任何提示指出这个包装器及其displayName已成冗余- 缺少
className的组件完全无法由其使用方进行样式设置
</red_flags>