关于 frontend-best-practices
frontend-best-practices 是一项面向前端开发和代码评审的技能,整理了框架无关的 UI 实践原则。它先识别项目框架,再按需加载对应参考,减少将一种框架的做法误用于另一种框架的风险。
主要功能
- 01先识别框架并加载对应参考
- 02组件架构与状态管理建议
- 03异步数据、表单与无障碍检查
- 04性能和 TypeScript 实践
- 05行为导向的测试与项目组织
适用场景
- 01构建或重构前端界面
- 02评审前端代码变更
- 03排查响应式逻辑问题
- 04检查无障碍、性能和类型安全
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ o-marsters-1997/ my-claude-code --skill frontend-best-practices安装后在 Claude Code 中输入/frontend-best-practices确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonevals/fixtures/package.react.jsonpackage.solid.jsonTodoList.tsxUserCard.tsxUserDashboard.tsxuseToggleSearch.tsxreferences/react.mdsolidjs.mdreferences/solidjs/async.mdsolid-2.mdstores.md详细介绍
前端
构建正确、易于访问、快速且类型安全的 UI 的最佳实践。以下原则与框架无关——无论你使用 React、SolidJS 还是其他技术,都适用。框架特定的实现方式放在按需加载的参考文档中,因为前端 Bug 最大的来源,就是把一种框架的思维模型套用到另一种框架上。
选择框架——先做这一步
通用原则告诉你目标是什么,但不会告诉你如何编写响应式代码,因为不同框架的做法差异很大;弄错这一点是“我的 UI 不更新”和“这里重新渲染太多次”这类 Bug 最常见的原因。因此,在编写或审查响应式逻辑之前,先检测框架并加载一份对应的参考文档。
按以下顺序检测;遇到第一个信号就停止:
- 用户明确说明。 如果用户说出了框架名称(“这是一个 Solid 项目”),就按用户所说的处理。
package.json依赖项——这是最有力的信号。在 monorepo 中,读取离要编辑的文件最近的package.json,而不是根目录下的那个。react/react-dom(或next)→ 加载references/react.mdsolid-js/@solidjs/*(例如@solidjs/router、@solidjs/start)→ 加载references/solidjs.mdtsconfig.json中的"jsxImportSource"可用于确认:"solid-js"或"@solidjs/web"表示使用 Solid。
- 当前文件的扩展名和导入。 使用
import … from "react"或调用useState等 Hook 的.tsx/.jsx文件 → React。使用import { createSignal } from "solid-js"或包含<For>/<Show>的 JSX → SolidJS。(注意:两者都使用.tsx,因此仅凭扩展名无法判断——请查看导入语句。)
# Quick check:
grep -E '"(react|react-dom|next|solid-js|@solidjs/[^"]+|solid-start)"' package.json
grep -E '"jsxImportSource"' tsconfig*.json
只加载匹配的参考文档——不要两份都加载。尚无对应参考文档的框架(Vue、Svelte)只采用通用原则;无需询问。如果 React 和 Solid 的信号确实冲突,或者两者的信号都不存在(例如空仓库中的新文件),只询问用户一次,然后继续。
由 hx-* 属性驱动的服务端渲染模板不属于组件框架:在 Go html/template 应用中,请改用 htmx-go-templates 技能,包括为其提供支持的 TS 模块。
为何这很重要(核心差异): React 会在每次渲染时重新执行整个组件函数,并依赖依赖项数组和记忆化来避免不必要的工作和重新渲染。SolidJS 则只运行每个组件函数一次,并使用细粒度信号仅更新发生变化的确切 DOM 节点——没有需要优化掉的重新渲染。这一处差异会彻底改变记忆化、副作用、派生状态、列表渲染和属性方面的指导。参考文档会用各自框架的模型重新阐述每个通用主题——请阅读对应文档,不要从下方原则自行推断。
如需日后添加框架:新增一个遵循分类标题结构的 references/<framework>.md,并在上方添加一条检测规则。通用正文无需改动。
通用原则
这些原则与框架无关。每条都对应每份参考文档中名称相同、顺序一致的标题,框架特定的实现细节会在其中说明。
1. 组件架构与组合
优先使用组合,而非继承或通过布尔属性进行配置。一个逐渐增加 isModal、isCompact、variant 等标记的组件,往往是两三个组件披着同一件外套——应将它们拆分。需要灵活组合而不是不断扩大的属性矩阵时,可使用复合组件(由父组件协调相关子组件)、渲染属性 / 插槽,以及 as 风格的多态组件。
让每个组件只负责一项职责,并提供简洁、完全类型化的属性 API,用来描述它接受什么,而不是如何实现。避免属性逐层传递:把一个值传过好几层未使用它的组件是一种代码异味——可以提升状态、通过上下文共享,或调整结构,让使用者更靠近数据源。检验标准:读者应该只看属性就能理解组件的约定。
2. 状态与数据流
区分服务端状态(从 API 获取的数据——它在别处有权威来源,会过时,需要缓存和重新验证)和客户端/本地状态(客户端拥有的 UI 相关状态:打开/关闭、选中的标签页、填写中的表单)。不要把获取的服务端数据随意存放在本地状态中再手动同步——这会导致 UI 过时且不一致。前者应使用服务端状态/查询层。
让状态与使用它的组件就近放置,只在最近的、需要它的共同祖先处提升状态——不要再往上提升。上下文应用于真正共享且低频变化的状态(主题、身份验证、区域设置),而不是用作全局收纳箱。状态转换变得复杂或相互依赖时,用 reducer/状态机对其建模,让无效状态无法表示。优先使用派生状态——由现有状态计算得出——而不是存储一个必须保持同步的副本。
3. 异步与数据获取
将每个异步操作明确建模为四种状态:加载、错误、空和成功。空状态(请求成功但未返回任何内容)最容易被忽略,而且它与加载状态的 UI 不同。不要对可能为 undefined 的值直接渲染 data.map(...)。
缓存并合并重复请求,避免同一屏幕多次获取相同数据。防范竞态条件:当输入变化快于响应返回时(例如边输入边搜索),过时的响应可能覆盖最新结果——应取消或忽略乱序返回的结果。使用框架的 suspense/异步边界原语,以声明式方式处理加载和错误,而不是到处散布 if (loading) 分支。
4. 表单与验证
优先使用受控输入,让组件状态成为字段值的唯一事实来源。定义一个模式(Zod),并据此推导 TypeScript 类型,而不是分别声明类型和验证器,导致两者逐渐不一致。在输入边界验证不可信输入——客户端验证是为了改善用户体验,但绝不能将其视为正确性或安全性的保障;服务端也必须验证。
确保错误信息易于访问:将每条错误信息与对应字段关联(aria-describedby),提交失败时将焦点移至第一个错误处,并播报验证结果,避免屏幕阅读器用户困在一个悄无声息地拒绝提交的表单中。
5. 无障碍
优先使用语义化 HTML——真正的 <button>、<nav>、<label>、<dialog> 能免费提供键盘行为、焦点和屏幕阅读器语义。只有在语义化 HTML 无法满足需求时才添加 ARIA;错误使用 ARIA 比不使用更糟。每个交互元素都必须可通过键盘访问和操作,并提供清晰可见的焦点状态和合理的焦点管理(在模态框中锁定焦点,关闭时恢复焦点)。达到 WCAG 2.1 AA 对比度标准(普通文本为 4.5:1)。将自动化检查(axe、Lighthouse)作为最低要求——它们只能发现大约三分之一的问题,因此也要亲自用 Tab 键遍历 UI。
6. 性能与渲染
先分析,再优化。 大多数感知到的卡顿都源自少数真正的瓶颈,而非普遍低效——使用性能分析器进行测量,并修复实际的热点。谨慎进行记忆化,只在测量结果证明有必要时使用,而不是条件反射式地使用(记忆化的工作方式及其必要性因框架而异——请参阅参考资料)。对首屏以下内容和路由级代码块进行代码拆分和延迟加载。对长列表进行虚拟化,只渲染视口中的行,而不是数千个屏幕外节点。分析打包内容,找出意外引入的重量级依赖,并优化图像(选择合适的格式和尺寸,启用延迟加载)。关注 Core Web Vitals(LCP、INP、CLS)——它们是面向用户的性能评分指标。
7. TypeScript 规范
启用严格模式,以及 noUncheckedIndexedAccess(数组访问的类型为 T | undefined)和
exactOptionalPropertyTypes。将互斥的形状建模为判别联合类型,并穷尽处理它们(添加变体时,never 默认分支会导致构建失败)。使用类型守卫收窄未知输入。优先使用 satisfies 而非 as——前者会检查值是否符合类型,而不会扩大类型或屏蔽错误;后者则是未经检查的断言。使用带约束的泛型构建可复用的组件和钩子,并使用工具类型、映射类型和条件类型推导类型,而不是重复声明。根据 schema(Zod)推导类型,让运行时验证和编译时类型共享同一来源。精确地标注组件属性、钩子返回值和事件处理器的类型——这些签名是首要的文档。
8. 测试原则
测试行为和渲染结果,而不是内部实现细节——断言用户能看到什么、能做什么,而不是调用了哪些函数或内部状态是什么。这样,测试才能经受重构。避免浅渲染,渲染真实内容。在网络边界模拟网络(拦截 HTTP),而不是为自己的模块打桩,这样测试才能覆盖真实的代码路径。
本技能不会重新讲解测试运行器。关于 TDD 工作流(红-绿-重构,先编写测试),请参考 tdd 技能。关于端到端和浏览器测试,请参考 playwright 技能。请按名称使用这些技能;不要在此处重新编写它们的指导内容。
9. 项目结构与工具
按功能组织(每个功能使用一个文件夹,集中放置其组件、钩子和测试),而不是按类型组织(使用 components/、hooks/、utils/ 这类大型文件夹,将同一功能分散到整个目录树中)。将测试和辅助工具与其服务的代码放在一起。优先使用具名导出(便于搜索、重构安全、避免导入时重命名不一致),并保持文件名和标识符命名一致。
基础工具:使用 Vite 进行开发和构建,使用 pnpm 安装依赖,使用 ESLint(扁平配置)和 Prettier 进行 lint 和格式化。每次提交前运行类型检查和 lint(通过 pre-commit 钩子或 CI 门禁),确保有问题的类型不会进入主分支。
如何应用此技能
构建或重构时:阅读上文中相关的通用原则,然后加载匹配的框架参考,了解具体实现方式。审查代码时,根据差异逐项检查这九个主题——但只指出确实有问题的地方,并说明原因,不要照本宣科地复述规则。