关于 react-dev
FixMyBerlin/FMC 的 React 与 TypeScript 组件规范技能,重点涵盖类型编写和审慎使用 `useEffect`。面向编写或审查 React 代码的开发者,并将路由及服务端工作指向相关技能。
主要功能
- 01React 19 组件与 ref 类型
- 02为 `useEffect` 函数命名
- 03何时避免使用 Effect 的指引
- 04React Compiler 与 Oxlint 约定
- 05React 19 API 使用说明
适用场景
- 01为组件属性和事件处理器添加类型
- 02审查或编写 Effect 逻辑
- 03避免用 Effect 处理派生状态或数据获取
- 04遵循 React Compiler 与 lint 约定
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fixmyberlin/ fixmyskills --skill react-dev安装后在 Claude Code 中输入/react-dev确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/generic-components.mdreferences/effects.mdevent-handlers.mdreact-19-patterns.mdtanstack-router.md详细介绍
LLM 参考资料: react.dev/llms.txt · tanstack.com/llms.txt · 关键 effect 页面:你可能不需要 Effect
React + TypeScript (FMC)
FMC 关于 React 类型定义和 useEffect 使用规范(命名、作用域、何时跳过)的约定。路由和服务端 I/O 由相关技能负责(见下文)。
FMC 技术栈(必需)
| 要求 | 规则 |
|---|---|
| React 19 | 目标版本为 19.x;新代码中不得使用 forwardRef / useFormState / useActionState;表单使用 TanStack Form |
| React Compiler | 在应用构建中启用 — 默认自动记忆化 |
| Lint | Oxlint react 插件 — 默认启用原生 Compiler 规则,另加 'react/unsupported-syntax': 'error';不使用 eslint-plugin-react-hooks jsPlugin |
| 记忆化 | 默认不要添加 useMemo / useCallback / memo;仅在性能分析或 lint 规则要求时添加 |
| TanStack Start | 服务端 I/O 和变更操作 → tanstack-start-conventions(本技能仅涵盖组件类型定义) |
| 数据获取 | 路由加载器 + React Query — 不使用 useEffect 获取数据(参见 tanstack-router-conventions) |
文档:React Compiler · React 规则 · Oxc React Compiler 支持
相关技能
| 主题 | 技能 |
|---|---|
路由、加载器、validateSearch | tanstack-router-conventions |
| URL 状态(优先使用路由搜索参数) | tanstack-router-conventions |
| SSR、服务端函数、Start 布局 | tanstack-start-conventions |
| 文件夹布局、精简路由(Start) | 技能 tanstack-start-conventions → references/app-structure.md |
createServerFn、服务端变更操作 | 技能 tanstack-start-conventions → references/server-functions.md |
| 客户端存储(不要使用 context + reducer) | zustand-state-management |
| 语义化 HTML(遗留的 div/span) | 技能 unslop-code → references/semantic-html.md |
类型标注说明
- Props: 使用
React.ComponentPropsWithoutRef<'button'>扩展原生元素(不包含 ref);使用children: React.ReactNode,不要使用JSX.Element。 - 变体 props: 使用判别联合(
{ variant: 'link'; href: string } | { variant: 'button'; onClick: () => void }),并根据判别字段缩小类型范围。 - ref: 在 React 19 中是普通 prop,不需要新增
forwardRef→ react-19-patterns.md。 - 事件: 使用具体的
React.*Event<HTMLElement>类型,以便为currentTarget提供类型;不要使用any→ event-handlers.md。 - Hooks: 对
null、[]和联合类型,显式使用useState<User | null>(null);对 DOM 使用useRef<HTMLInputElement>(null)+?.,对可变容器使用useRef(0);对useReducer操作使用判别联合;使用createContext<T | null>(null)+ 会抛出异常的自定义 hook。外部存储:useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)。 - 路由 hooks: 在
createFileRoute组件内使用Route.useParams()/Route.useSearch();validateSearch放在路由文件中 → tanstack-router.md。
useEffect——命名、作用范围以及何时跳过
Effect 是用于与外部系统(非 React 小组件、浏览器 API、地图 SDK、订阅、分析)同步的备用方案。如果不涉及外部系统,通常就不应使用 Effect。
为每个 useEffect 命名
始终向 useEffect 传入具名函数表达式,不要使用匿名箭头函数。这是新代码和修改后代码的默认风格,包括自定义 hooks 内的 Effect。也要为非简单的清理函数命名。
useEffect(
function updateDocumentTitle() {
document.title = `${count} items`
},
[count],
)
useEffect(function pollServerForUpdates() {
const intervalId = setInterval(/* ... */, 5000);
return function stopPollingServer() {
clearInterval(intervalId);
};
}, [serverId]);
从命名中发现问题
在键盘前审查设计时,命名就是一个信号。详情请参阅:effects.md。
| 信号 | 含义 | 修正 |
|---|---|---|
| 名称需要用到 “and” / “also” | 一个 Effect 中包含无关的职责 | 拆分为多个 Effect,每个 Effect 使用一个名称 |
syncDerivedValue、updateStateFromState、setXBasedOnY | 派生状态或状态之间的同步 | 在渲染期间派生 |
notifyParentOfChange、reportStateToParent | 根据子组件状态更新父组件 | 在事件处理函数中调用、向上提升数据,或将组件设为受控组件 |
resetFormOnSubmitFlag | 通过状态跳转表达用户意图 | 在事件处理函数中处理 |
persistOnClose、syncWhenClosed、saveDraftOnDismiss | 关闭时产生的副作用/打开状态的 UI | 使用处理函数,而不是监听 isOpen 的 Effect(effects.md §3) |
明确的外部动词:connectTo…、subscribeTo…、initialize…、synchronize…(浏览器、网络、地图 SDK) | 通常属于合理的 Effect 范畴 | 仍应为它们命名;需要时添加清理逻辑 |
如果诚实的命名听起来像是在处理 React 内部事务,那么这段代码就应该放在渲染过程中、事件处理函数中,或改用其他模式。
快速参考
| 情况 | 不要这样做 | 应该这样做 |
|---|---|---|
| 从 props/state 派生状态 | useState + useEffect | 在渲染期间计算(默认不使用 useMemo,Compiler 已启用) |
| 在 prop 变化时重置状态 | 使用 useEffect 和 setState | 使用 key prop |
| 响应用户事件 | 使用 useEffect 监听状态 | 直接在事件处理程序中处理 |
| 关闭时持久化/同步 | 使用 useEffect 监听 isOpen | 使用关闭处理程序(或 onCloseComplete) |
| 通知父组件发生变化 | 使用 useEffect 调用 onChange | 在事件处理程序中调用,或使用受控组件 |
| 从子组件向父组件传递数据 | 使用 Effect 调用 onFetched | 由父组件获取数据并向下传递 |
| 外部存储 | 使用 Effect + useState | 使用 useSyncExternalStore |
| 获取数据 | 使用 useEffect | 使用路由加载器 + React Query |
Strict Mode(开发环境): 挂载时会运行两次 Effect 的设置逻辑。为订阅和计时器添加清理逻辑;对仅需初始化一次的操作进行保护(模块级 didInit 守卫,参见 effects.md §6)。
Effects 与 React Compiler / lint
- Compiler 不会取代用于外部同步的 Effects。记忆化方式的变化可能改变 Effect 重新运行的时机——应修正 Effect 的设计(依赖项、拆分、
useEffectEvent),而不是禁用 Compiler。 - Lint:Oxlint 原生 React Compiler 规则(
react/set-state-in-effect、react/immutability,……)。
useEffectEvent
适用于必须保持订阅(连接、间隔、监听器)的 Effect,同时回调中的一部分会读取最新的 props/state。示例:effects.md §7。
- 使用类似用户可见事件的名称:
onConnected、onTick,不要用onMount/onUpdate。 - 只能从 Effects(或同一组件中的其他 Effect Events)调用;不要在渲染期间、事件处理程序中调用,也不要作为 prop 传给子组件。
- 从依赖数组中省略(由 lint 强制);绝不要用它隐藏本应触发 Effect 重新运行的依赖项。
React 19 API(参考)
| API | 用途 | 文档 |
|---|---|---|
use() | 解包 promise/context(可能触发挂起) | use |
useOptimistic | 乐观 UI | useOptimistic |
useTransition | 非紧急更新 | useTransition |
useEffectEvent | Effect 内的稳定回调 | useEffectEvent |
规则
始终
- 使用命名的
useEffect函数;启用 React Compiler;使用 oxlint 原生react/*Compiler 规则 - 优先使用语义化 HTML,而非通用的
div/span— skillunslop-code→references/semantic-html.md - 在 React 19 中将
ref作为 prop
绝不
- 新增
forwardRef/useFormState/useActionState(表单:TanStack Form) - 无充分理由时默认使用
useMemo/useCallback/memo - 使用
useEffect获取应用数据(改用 loaders/Query) - 等待传给
use()的 promise 完成,因为要将其交给流式传输
参考资料
- effects.md — 编写或审查
useEffect时,或效果名称命中上方的异味表时阅读(使用key重置、手势触发后持久化、父级数据流、初始化保护、useEffectEvent示例) - event-handlers.md — 为点击/提交/更改以外的事件处理器指定类型,或通过属性转发事件时阅读
- react-19-patterns.md — 将
ref作为属性、使用use(),或将旧代码库迁移到 React 19 时阅读 - generic-components.md — 编写泛型组件时阅读(受约束的
T extends HasId、带访问器的Select<T>) - tanstack-router.md — 为路由钩子指定类型时阅读(权威参考:
tanstack-router-conventions→router-typescript.md) - Neciu Dan — 开始为你的 useEffect 函数命名