plugins/languages/typescript/skills/react/SKILL.md
TypeScript / JavaScript React 开发规范,覆盖 React 19 Server Components / Server Actions / use() hook / useActionState / useOptimistic / useFormStatus / Suspense、Next.js 15 App Router、Route Handlers、React Compiler 自动 memo、TanStack Query 5、TanStack Router、Zustand 状态、自定义 hook AbortController 模板。Use when 开发 React 组件、页面路由、SSR、状态管理、表单处理、数据获取,或用户提到 "React"、"Next.js"、"Server Components"、"use client"、"Server Actions"、"useState"、"JSX"、"App Router"。
npx skillsauth add lazygophers/ccplugin typescript-reactInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
本 skill 同时覆盖 JavaScript 项目;示例以 TS 为主,JS 项目去掉类型即可。
React 19 默认 Server Components;"use client" 是边界标记,不滥用。
typescript-core — 工具链与基线typescript-async — AbortController, Suspense 配合typescript-security — XSS, dangerouslySetInnerHTMLReact 19 Compiler 自动记忆化组件、hooks、JSX,手写 useMemo / useCallback / memo 改为可选。仅在 profiler 证实瓶颈时手动优化。
// ❌ React 18 手写 (Compiler 启用后冗余)
const v = useMemo(() => compute(data), [data]);
const onClick = useCallback(() => f(id), [id]);
export default React.memo(MyComp);
// ✅ React 19 + Compiler
const v = compute(data);
const onClick = () => f(id);
export default MyComp;
// app/users/page.tsx
import { db } from "@/lib/db";
export default async function UsersPage() {
const users = await db.user.findMany();
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}
// app/actions.ts
"use server";
import { z } from "zod";
import { revalidatePath } from "next/cache";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.email(),
});
export async function createUser(_: unknown, formData: FormData) {
const r = CreateUserSchema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
});
if (!r.success) return { errors: z.flattenError(r.error).fieldErrors };
await db.user.create({ data: r.data });
revalidatePath("/users");
return { ok: true as const };
}
import { use, Suspense } from "react";
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // suspend
return <div>{user.name}</div>;
}
<Suspense fallback={<Loading />}>
<UserProfile userPromise={fetchUser(id)} />
</Suspense>
"use client";
import { useActionState, useOptimistic } from "react";
import { useFormStatus } from "react-dom";
function CreateUserForm() {
const [state, action, isPending] = useActionState(createUser, null);
return (
<form action={action}>
<input name="name" required />
<input name="email" type="email" required />
{state?.errors && <p>{JSON.stringify(state.errors)}</p>}
<SubmitButton />
</form>
);
}
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "..." : "Create"}</button>;
}
// 乐观更新
const [optimistic, addOptimistic] = useOptimistic(
messages,
(prev, next) => [...prev, { ...next, sending: true }],
);
// document metadata 原生
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
// ✅ 普通函数
type UserCardProps = {
user: User;
onSelect?: (id: string) => void;
};
export function UserCard({ user, onSelect }: UserCardProps) {
return (
<div onClick={() => onSelect?.(user.id)}>
<h3>{user.name}</h3>
</div>
);
}
// ❌ 禁 React.FC (隐式 children、泛型不友好)
export function useFetch<T>(url: string) {
const [state, setState] = useState<AsyncState<T>>({ status: "idle" });
useEffect(() => {
const ctrl = new AbortController();
setState({ status: "loading" });
fetch(url, { signal: ctrl.signal })
.then((r) => r.json() as Promise<T>)
.then((data) => setState({ status: "success", data }))
.catch((error: unknown) => {
if (!ctrl.signal.aborted) setState({ status: "error", error: error as Error });
});
return () => ctrl.abort();
}, [url]);
return state;
}
| 场景 | 推荐 |
|------|------|
| Next.js App Router | Server Components + fetch() + revalidate |
| Vite SPA | TanStack Query 5 (useSuspenseQuery + Suspense) |
| Realtime / 订阅 | WebSocket / SSE in useEffect + AbortController |
| 表单 | Server Actions / TanStack Form |
// TanStack Query 5 + Suspense
import { useSuspenseQuery } from '@tanstack/react-query';
function User({ id }: { id: string }) {
const { data } = useSuspenseQuery({
queryKey: ['user', id],
queryFn: ({ signal }) => fetch(`/api/users/${id}`, { signal }).then(r => r.json()),
});
return <div>{data.name}</div>;
}
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = { title: "App", description: "..." };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html lang="en"><body>{children}</body></html>;
}
// app/api/users/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body: unknown = await request.json();
const r = CreateUserSchema.safeParse(body);
if (!r.success) {
return NextResponse.json({ errors: z.flattenError(r.error) }, { status: 400 });
}
const user = await db.user.create({ data: r.data });
return NextResponse.json(user, { status: 201 });
}
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
<Suspense fallback={<Spinner />}><Dashboard /></Suspense>
useState / useReduceruseSearchParams, TanStack Router: typed)所有 React 19 API 在 JS 项目同样可用,去掉类型注解即可。JS 项目用 JSDoc 标注 props:
/**
* @param {{ user: User; onSelect?: (id: string) => void }} props
*/
export function UserCard({ user, onSelect }) {
return <div onClick={() => onSelect?.(user.id)}><h3>{user.name}</h3></div>;
}
| 现象 | 问题 | 严重 |
|------|------|------|
| React.FC | 隐式 children、泛型受限 | 中 |
| class 组件 | 函数组件 + Hooks | 高 |
| useEffect 内 fetch (无 abort) | TanStack Query 或加 AbortController | 中 |
| "use client" 顶层滥用 | 应最小化 client 边界 | 中 |
| 无 Suspense 边界 | 异步组件需 fallback | 高 |
| Server Actions 无 Zod | 服务端必须验证 | 高 |
| 手写 memo (Compiler 已开) | 冗余 | 低 |
| 无 key / index as key | 稳定 ID 作 key | 高 |
| 依赖数组缺失 | Biome/ESLint react-hooks 修复 | 高 |
| dangerouslySetInnerHTML 无清理 | DOMPurify | 高 |
React.FC)"use client" 仅边界react-compiler babel plugin)useActionStatetools
UI/UX 与布局设计——做界面布局/结构/导航/组件/交互的设计决策。触发:做UI/UX/布局/排版/导航/组件/交互/栅格/响应式/图表选型/字体配对。按媒介路由 HTML/Web、原生 App(iOS/Android/桌面)、CLI、TUI。需后端动态系统不适用;配色/主题/色板走姊妹 skill design-color。
tools
主题与配色设计——做颜色搭配/调色板/主题/品牌色阶/暗模式的设计决策。触发:选配色/调色/主题/色板/品牌色/暗模式/对比度/色盲/UI风格。按媒介路由 HTML/Web(CSS变量)、原生App(平台token)、CLI(ANSI)、TUI(真彩/256/16降级)。保证可访问性(对比度/色盲安全)。需后端动态系统不适用;UI/UX 布局/组件/交互走姊妹 skill design-uiux。
tools
跨任意组件(plugin/skill/agent/command)的验证驱动优化循环纪律 skill。当用户要优化某个已有组件却无明确方向、或要防止改了反而更差(自评乐观偏差 / 多维同改归因失效 / 为凑分加废话膨胀)、或要把一套通用「评分→单变量改→改后验证严格更好才留否则回滚→触顶停」的纪律套到任意组件上时使用。管优化过程本身的纪律(validation gate / ratchet / 独立验证 / 触顶停),不评单组件深度(交 skill-dev),不查插件接线(交 plugin-dev)。仅手动 /optimize-any 触发。
data-ai
两层规则记忆 (基于 .skein/spec)。planning 时 recall 召回相关规则、task finish 后 sediment 沉淀学习 + prune 自动精简过期/重复/断链规则。core 常驻硬规 + recall 按需召回, 经判定门自动写盘 (不逐次问用户)。产出 .skein/spec 下 core/recall 规则文件 + index。另支持空仓 bootstrap 播种规则基线、记忆大面积失效 (大重构/换栈) 时 reconstruct 可逆归档后按项目类型分型重建、maintain 手动体检 (超预算/stale/断链/重复/废弃, --apply 自动修复)、auto-fix (Stop hook 写 .pending-fix 标记 → main 派 skein-specer bg 跑 maintain --apply 全自动修, 断链只报告)。