首页
/ Bruno 的 React 代码审查清单:`bruno-app` 前端评审规则与主题令牌机制解析

Bruno 的 React 代码审查清单:`bruno-app` 前端评审规则与主题令牌机制解析

2026-09-07 17:37:22作者:邬祺芯Juliet

这篇文章以 Bruno 仓库内置 AI 代码审查技能中的 React 评审者文件 react.md 为主体,完整拆解它定义的审查范围、blocker/suggestion 两级违规清单,以及背后真实生效的主题令牌(theme token)校验机制。读完后,你可以把同一套规则迁移到自己项目的前端评审流程中,并理解为什么"主题令牌不一致"在 Bruno 中会被直接判定为阻断级缺陷。

一、评审范围与输出契约

react.mdcode-review 技能 下的一个"评审者人格"文件,其开篇用一句话锁定职责边界:

  • Scope(审查范围): packages/bruno-app/** —— 即 Bruno 桌面客户端(React 前端)的全部代码。Bruno 是一个 monorepo,packages/bruno-app/ 存放 Electron 渲染进程侧的 UI,其余包(CLI、请求引擎、文件存储等)不在此评审者管辖之内。

评审者需要同时遵循两份上游文档,形成"通用契约 + 专项清单"的结构:

  1. _contract.md 定义所有评审者共享的人格与输出契约
    • 人格要求:面向 TypeScript / JavaScript / Node.js / Electron 的企业级资深评审者,每条发现只写一句清晰的话,不因人而软化严重度;
    • 输出格式为扁平列表,一行一条发现:<blocker|suggestion|nit> | <file>:<line> | <one-sentence finding>
    • 范围干净时只返回 no findings,禁止为了填满列表而编造 nit;
    • 每条发现必须落在真实代码上——"仓库与文档不一致时以仓库为准,且不得引用未验证过的行号或虚构示例值"。
  2. CODING_STANDARDS.md§React 小节,是组件代码评审的基线标准(见下节)。

二、基线标准:CODING_STANDARDS.md §React 中的硬性约定

react.md 明确要求"对照 CODING_STANDARDS.md §React 审查被修改的组件(必须通读该文件)"。该小节位于 CODING_STANDARDS.md 的 "UI Specific instructions → React" 部分,核心条目与评审规则一一对应:

约定 级别 说明
颜色一律走 styled-components 的 theme prop,而不是 CSS 变量 MUST 在 styled component 或任何使用 styled-components 的组件上下文中生效
styled-components 负责自身与子组件样式(也可改布局),Tailwind 只用于布局类样式,不得定义颜色 规则 两套体系的分工边界
业务逻辑、数据获取、副作用优先封装为自定义 Hook MUST
非必要不使用 useEffect,优先派生状态(derived state)与事件处理器 MUST 这是 React 反模式的第一来源
useMemo/useCallback 只在确有需要时记忆化,且优先把逻辑移入 Hook SHOULD 防止"无谓 memo"
禁止命名空间式访问 Hook(React.useCallback(...)),必须直接具名导入 MUST 正确写法:import { useCallback, useMemo, useState } from "react";
可测试元素加 data-testid(供 Playwright 使用) MUST 对应仓库中 tests/**/*.spec.ts 的 E2E 测试体系
工具函数就近共置:组件专属的放组件旁,共享的放公共目录 规则 对应 bruno-app-layout 的放置规则
避免受控/非受控状态混用;状态需要单一事实来源,禁止"先用 props 算一遍、组件内再算一遍" MUST
能用派生状态变量就不要新增 useState SHOULD

评审清单中几乎每一条 blocker 都能在这个表里找到出处——这正是"以编码标准为基线、以专项清单为执行细则"的设计意图。

三、blocker 级清单一:React 正确性缺陷

react.md 把下列情形全部定为 blocker(阻断级),要求报告 file:line

  1. 本可用派生状态/事件处理器/自定义 Hook 解决的 useEffect。与标准中"Avoid useEffect unless absolutely needed"直接呼应:副作用里做状态同步是典型的冗余状态,会引入不一致窗口。
  2. 硬编码颜色:hex / rgb / hsl / 命名色(如 #fffrgb(0,0,0)lightblue)替代了 styled-components 的 theme prop。清单特别要求评审者"验证该令牌路径确实存在于 theme 对象上"——即不能为了消掉一个硬编码值而引用一个不存在的 theme.xxx.yyy
  3. 命名空间式 Hook 导入React.useX):违反上表中的导入约定。
  4. 受控与非受控状态混用的组件:状态没有单一事实来源。
  5. 条件早退之后调用 Hook:违反 Hooks 规则,直接破坏 Hook 调用顺序稳定性。

其中第 2 条之所以在 Bruno 中格外严重,是因为它牵连下一节的主题体系:一个硬编码颜色会"破坏其余 12 个主题"。

四、blocker 级清单二:目录布局(bruno-app-layout)

react.md 第二条 blocker 是"任何违反 bruno-app-layout.md 的改动,包含'组件专属 vs 共享'的放置问题"。该规则文件(frontmatter 限定作用于 packages/bruno-app/**)把布局视为"承载性约定"(load-bearing),放错文件等同于架构缺陷,与依赖方向违规同级、同样阻断。四条核心规则:

  • 组件拥有自己的目录<ComponentName>/index.js(x),绝不写成 <ComponentName>.js——任何嵌套深度都如此,包括组件的子组件;Hook(src/hooks/useX/index.js)与 src/ui/ 基元同样适用。
  • 组件拥有的一切放在其目录内StyledWrapper.js、spec、组件专属工具。向上"借用"父组件的 StyledWrapper.js,或把样式留在组件目录外,等于"没人拥有它们"。
  • src/ui/ 是叶子层:只放与 app 无关的展示型基元,且不得反向导入 src/components/——依赖边只能 components → ui
  • utils/ 只放"被调用的无状态辅助函数":被"注册"到某处的模块(编辑器扩展、插件、node view、keymap)属于行为或配置,归入 utils/ 会掩盖其"是某个 schema 或文档契约的一部分"的事实,应单独立目录。

规则文件末尾还列出了两条既成偏差(bare PascalCase.js 组件文件、ui/MenuDropdown 导入 components/Dropdown)——评审规则特意注明:"文件末尾记录的偏差不是发现项"。这是清单设计中很关键的一笔:只对变更负责,不考古存量

五、blocker 级清单三:主题令牌与 schema 的一致性

这是 react.md 中最有"Bruno 特色"的一条 blocker,它描述的是一条真实存在的运行时校验链:

themes/light|dark/*.js 中定义了新的主题令牌,却没有在 themes/schema/oss.js 中补充对应属性;或者令牌已加入 schema 却在 13 个主题文件中的某些文件里缺失。由于 schema 是 additionalProperties: false,且 providers/Theme/index.js 会在运行时按它做校验,任一侧缺漏都会使整个主题失效,用户被静默回退到默认主题并收到一个错误 toast

结合仓库源码可以逐环验证这条链路:

  • 13 个主题文件确实存在packages/bruno-app/src/themes/light/ 下有 5 个(light.jsvscode.jscatppuccin-latte.jslight-pastel.jslight-monochrome.js),packages/bruno-app/src/themes/dark/ 下有 8 个(dark.jsvscode.jsnord.js、三个 catppuccin-*.jsdark-pastel.jsdark-monochrome.js),合计 13 个。这就是"破坏其余 12 个主题"说法的来源。
  • schema 确实是 additionalProperties: falseoss.js 全文出现 120 余处 additionalProperties: false,从顶层分组(primaryaccentsbackgroundstatus 等)一路锁死到叶子对象,并配合 required 数组强制必填。例如 primary 组要求 solid/text/strong/subtle 四键齐全且禁止多余键。
  • 运行时校验与回退providers/Theme/index.js 在文件顶部导入 jsonschemaValidatorreact-hot-toasttoast,并在计算主题对象时先做存在性检查——选中变体缺失或无效时回退到 themes.light / themes.dark,并对非默认变体弹出 toast 提示。评审清单中"整主题失效、静默回退默认 + 错误 toast"的后果描述与此实现一致。

从这条机制可以推断出评审规则背后的产品考量:主题令牌是跨 13 套配色的一致性契约,"少一个属性"不会抛异常崩溃,而是让整个主题静默失效——这类缺陷极难被肉眼发现,因此只能靠评审期阻断。

六、suggestion 级清单:性能、边界与体验细节

react.md 的 suggestion(建议级)条目数量更多,覆盖五类:

  1. 记忆化失当:缺失的 memo 导致依赖数组失效或重型子组件重复渲染;以及反向的"给廉价原语无谓套 memo"。对应标准中"SHOULD: Memoize only when necessary"。
  2. Tailwind 越界:用 Tailwind 定义颜色(仅允许用于布局)。
  3. 可测试元素缺 data-testid:与仓库根下 tests/ 目录中大量 Playwright spec(要求"用 role / label / test id 等稳定选择器")形成配套。
  4. 令牌替换不保真:主题令牌替换后解析出的值与被替换的字面量不同;或同组值中有的转了主题化、有的仍硬编码(要求先到 themes/ 里把值定下来再替换)。
  5. 全局监听未加门槛document/window 监听器没有以"使其相关的状态"为前置条件(gate)——即使当前某个偶发细节让它恰好无害。
  6. EditableTable 列的门控缺失:新增列没有像同列表那样带上 readOnly/editMode 门控(渲染处形如 readOnly={column.readOnly})——结果是字段在 view / read-only 模式下依然可编辑,而变更处理器又会丢弃这次编辑,形成"看起来能改、实际白改"的矛盾状态。
  7. 乐观成功态无条件置位copied / saved / done 之类的成功状态不看操作是否真正完成就置为 true。文档给出的反例极具代表性:navigator.clipboard?.writeText 后直接 copied = true——在不可信(insecure)上下文中可选链调用会静默 no-op,用户却看到虚假的"已复制"确认。

这些建议级条目共同的特点是:不阻断合并,但会在特定环境/状态组合下制造真实的用户困惑,因此规则要求评审者"即使今天恰好无害"也要报告监听器未加门槛一类的问题。

七、涉及 Store 的改动:叠加 redux-store 规则

react.md 中还有前置条件:"对被改动的组件,按 CODING_STANDARDS.md §React 审查;若改动触及 store,还需参阅 redux-store.md"。该规则文件补充了 bruno-app 的 Redux 侧约束,评审时与 React 清单叠加生效,要点包括:

  • Store 基于 Redux Toolkit v1.8(而非 v2),configureStore 位于 src/providers/ReduxStore/index.js,其 reducer map 是唯一的 slice 权威清单;中间件为 getDefaultMiddleware().concat([...]) 且不带选项,因此 serializableCheck / immutableCheck 在开发期全部生效——状态必须保持可序列化
  • Reducer 直接改写 Immer draft(state.collections.push(...)item.name = ...),不应以 spread 克隆/返回新对象的方式"纠正"它。
  • slice 组织上的两个坑:reducer key 不等于文件夹名(如 globalEnvironments 来自 slices/global-environments.js);action type 前缀跟随 slice 的 name: 而非 reducer key,追踪 dispatch 时应 grep action type。

八、如何把这套清单落地为自己的评审流程

react.md 的结构可以提炼出一条可复用的设计模式:

  1. 范围先行:每个评审者文件第一行声明 Scope(glob 路径),使多评审者并行时互不越界;
  2. 基线与细则分层:把长期稳定的编码标准(CODING_STANDARDS.md)与具体人格的执行清单分离,基线改动时所有评审者自动跟进;
  3. 严重度与后果绑定:blocker 的判定标准不是"风格不好看",而是"会造成运行时失效/状态不一致/架构漂移",每条都写清后果(如主题静默回退);
  4. 对存量偏差显式豁免:写明哪些历史问题是"drift, not a second pattern",只对新变更追责;
  5. 输出契约机器可读<severity> | <file>:<line> | <一句话> 的扁平格式便于下游工具聚合、去重与统计。

适用前提与限制:这套规则深度绑定 Bruno 的具体实现——13 个主题文件、additionalProperties: false 的 schema、src/ui 叶子层约定——迁移时须替换为对应仓库的真实路径与约定;同时它面向"变更审查"(review changed components),不适合作为全量代码审计清单使用。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388