mcpField
R
react-advanced

react-advanced

github.com/trancong12102开发749 次安装6 周前更新
代码仓库

关于 react-advanced

为使用 TanStack 生态和 XState 构建现代 React 应用提供跨平台约定与架构指导,面向 React Web 和 React Native 开发者。内容规定数据、界面状态、表单与异步流程的职责划分,并说明常见陷阱。

主要功能

  1. 01区分服务端与客户端状态
  2. 02规定各类库的职责边界
  3. 03提供 XState 使用判断依据
  4. 04说明组件组合模式
  5. 05按主题提供参考文档

适用场景

  1. 01规划 React 应用的状态管理
  2. 02设计多步骤异步交互流程
  3. 03避免在 useEffect 中处理派生状态
  4. 04确定表单与服务端数据的管理方式

安装

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

$npx skills add https://github.com/trancong12102/agentskills --skill react-advanced

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
form.md
integration.md
react-query.md
table.md
testing.md
xstate.md
zod.md
zustand.md

详细介绍

React 高级:核心模式与约定(跨平台)

本技能定义了使用 TanStack 生态系统和 XState 构建现代 React 应用的规则、约定和架构决策。它有意采用明确的主张, 以避免常见陷阱并推行可扩展的模式。

这些模式在 Web 和 React Native 上完全相同。平台特定模式请参阅:

  • **Web:**参见 react-web-advanced(TanStack Router、Start、Virtual)
  • **React Native:**参见 react-native-advanced(Expo Router、FlashList、MMKV)

如需了解此处提及的任何库的详细 API 文档,请使用其他合适的工具 (文档查询、网页搜索等)——本技能侧重于如何以及为何使用这些工具, 而非完整的 API 内容。

禁用 useEffect

请勿将 useEffect 用于:

  • 获取数据——使用 React Query(useSuspenseQuery / useQuery)或路由加载器
  • 派生状态——在渲染期间计算,或使用 useMemo
  • 将状态与 props 同步——直接使用 prop,或通过 React key 重置
  • 响应用户事件——将逻辑放在事件处理程序中
  • 订阅外部存储——使用 useSyncExternalStore
  • 复杂的异步流程——使用带有 invoke / fromPromise 的 XState 状态机
useEffect 的适用场景

合理的情况:

  1. 与非 React 外部系统同步——DOM API、第三方组件 (地图、图表)、需要挂载/卸载生命周期的命令式库
  2. 订阅浏览器/原生 API 并进行清理——对于一次性场景,如果 useSyncExternalStore 过于 底层(WebSocket 连接、尺寸变化观察器)
  3. 挂载时进行分析/日志记录——不更新状态的即发即弃副作用
  4. 将 React Query 数据桥接到 XState——通过事件将服务端状态推送到状态机的 useEffect 桥接模式(参见 references/xstate.md)
使用 useMountEffect 执行仅在挂载时运行的副作用

当你有合理的、仅在挂载时运行的副作用(上面的第 1–3 种情况)时,请使用 useMountEffect 辅助函数,而不是直接使用 useEffect(fn, []):

// utils/useMountEffect.ts
import { useEffect, type EffectCallback } from "react";

// eslint-disable-next-line react-hooks/exhaustive-deps
const useMountEffect = (effect: EffectCallback) => useEffect(effect, []);

export default useMountEffect;

用法:

useMountEffect(() => {
  const plugin = $.myPlugin(ref.current);
  return () => {
    plugin.destroy();
  };
});

原因:直接使用 useEffect(fn, []) 会触发 react-hooks/exhaustive-deps lint 规则, 要求开发者证明空数组是有意为之。useMountEffect 会在代码中明确表达“挂载时运行一次”的意图, 并正确消除警告。

替代方案
用途(替代 useEffect)使用
获取数据useSuspenseQuery + 路由加载器预取
使用 Promiseuse() hook(React 19+)+ <Suspense>
派生/计算值直接计算或 useMemo
订阅外部存储useSyncExternalStore
延迟开销较大的渲染useDeferredValue / useTransition
编排复杂异步流程XState invoke 配合 fromPromise
prop 变化时重置状态组件上的 React key prop
用户触发的副作用直接使用事件处理程序

状态管理理念

服务端状态与客户端状态——切勿混为一谈

服务端状态(来自 API/数据库的数据)和 客户端状态(仅存在于客户端中的 UI 状态)是本质上不同的问题。将两者混在一起会导致数据过时、重复和难以处理的同步问题。

关注点归属方示例
服务端数据React Query用户、帖子、产品、订单
URL/路由状态Router路径参数、搜索参数(TanStack Router 或 Expo Router)
复杂 UI 流程XState多步骤向导、身份验证流程、拖放
共享客户端 UIZustand主题、侧边栏、选中项、全局筛选器、偏好设置
表单字段TanStack Form输入值、验证错误、提交
Schema 验证Zod搜索参数、表单验证器、API 契约
简单的本地 UIuseState切换、手风琴展开、输入焦点
决策流程图
Is the data from a server / API?
  YES -> React Query (queryOptions + useSuspenseQuery)
  NO -> Is it in the URL / route params?
    YES -> Router (platform-specific: TanStack Router or Expo Router)
    NO -> Is it a complex multi-state flow (3+ states, async, guards)?
      YES -> XState (useMachine / createActorContext)
      NO -> Is it a form field?
        YES -> TanStack Form (with Zod validators)
        NO -> Is it shared across components / trees?
          YES -> Zustand (create store + selectors)
          NO -> useState / useReducer
何时选择 XState,而不是 useState/useReducer

在以下情况下使用 XState:

  • 存在 3 个或更多互斥状态,且有明确定义的转换
  • 异步副作用必须在状态变化时取消(竞态条件)
  • 逻辑包含 守卫条件(用于限制状态转换的条件)
  • 需要为独立关注点设置 并行状态
  • 需要脱离 React 单独测试流程
  • 多个步骤需要支持 前进/后退导航(向导)

不要将 XState 用于简单的切换、单个布尔标志或计数器状态。这些情况应使用 useState。


架构:各库的职责

层库职责
服务端状态React Query获取、缓存、失效、后台重新获取
复杂 UI 状态XState状态机、Actor 模型、流程编排
共享客户端 UIZustand跨组件 UI 状态、偏好设置、选择项
表单生命周期TanStack Form字段值、验证、提交
Schema 验证Zod搜索参数、表单验证器、API 契约
数据展示TanStack Table无头排序、筛选、分页、分组
测试Vitest + TL单元、组件、集成、状态机测试
简单的本地状态useState切换、本地输入、组件作用域内的值
黄金法则:将 queryOptions 作为唯一事实来源

只定义一次查询选项,然后在各处导入——加载器、组件、失效处理:

export const postsQueryOptions = queryOptions({
  queryKey: ["posts"],
  queryFn: fetchPosts,
  staleTime: 30_000,
});

export const postQueryOptions = (postId: string) =>
  queryOptions({
    queryKey: ["posts", postId],
    queryFn: () => fetchPost(postId),
    staleTime: 30_000,
  });

组件组合

复合组件

当一组组件共享隐式状态时,使用基于 Context 的复合组件。由父组件管理状态;子组件通过 Context 使用该状态。对 Context 值进行记忆化处理。

插槽模式

使用命名属性进行类似插槽的组合(header、footer、actions)。避免深度嵌套的渲染属性树。

控制反转

如果为了处理调用方特定的行为而添加布尔属性或分支逻辑,应通过回调、reducer 或渲染函数将该逻辑交还给调用方。出现三个相似的 if 语句,就说明应该反转控制。


常见陷阱

  1. 在 useEffect 中派生状态 — 在 effect 中计算值并存入 useState 会导致重复渲染。应在渲染期间计算,或使用 useMemo。

  2. 在客户端状态中存储服务端数据 — 将 API 响应放入 Zustand/Redux,意味着 你需要自行负责缓存和失效处理。请改用 React Query。

  3. 在 useState 中重复存储 URL 状态 — 直接使用路由器的 search/param 钩子。

  4. 对同一数据同时使用 React Query 和 XState — React Query 负责获取和缓存。 XState 通过事件接收数据,仅负责编排。

  5. 在 XState 状态机中调用 React 钩子 — 钩子只能在 React 组件中使用。 在状态机中使用 fromPromise,通过 useEffect 事件桥接数据。

  6. 将数组索引用作键 — 使用稳定的 ID(item.id)。索引键会导致重排、插入或删除时 状态关联错误。

  7. 在组件内部定义组件 — 每次渲染都会创建新的组件类型, 导致 React 卸载并重新挂载组件。请在模块级别定义组件。

  8. 对高频状态使用 Context — 每次状态变化都会导致 Context 的所有使用者重新渲染。 对共享且快速变化的值,使用带选择器的 Zustand(参见 references/zustand.md);如果状态仅限于组件,则使用本地状态。

  9. Zod 搜索参数架构未使用 .catch() — .default() 只处理缺失的键; .catch() 还会处理格式错误的 URL 中的无效值。


参考文件

使用特定库时,请阅读相关参考文件:

文件阅读时机
references/react-query.md查询模式、变更、缓存、Suspense 集成
references/table.md列定义、排序、筛选、分页、服务端操作
references/form.md字段验证、数组、架构验证、性能
references/xstate.md状态机、Actor、身份验证流程、向导、React 集成
references/zustand.md共享客户端 UI 状态、选择器、切片、中间件、原生存储
references/zod.md架构验证、v4 API、Form 集成、错误处理
references/testing.mdVitest 配置、Testing Library、MSW、测试 Query/Form/XState
references/integration.md组合使用库:Zustand+XState、Query+XState 桥接

评论

支持 Markdown