pedantic-react
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePedantic React
严谨型React规范
Opinionated house style for React + TypeScript frontends.
一套针对React + TypeScript前端项目的约定式内部编码风格。
TypeScript
TypeScript
- Prefer explicit types at all boundaries (props, return types, API responses).
- Use for type-only imports.
import type - Type component props explicitly with . Use
interfacewhen children are accepted.PropsWithChildren - Wrap component prop inputs in .
Readonly<...> - Prefer over
unknown; narrow with type guards.any - Avoid non-null assertions (); prefer optional chaining and explicit guards.
!
- 在所有边界(Props、返回类型、API响应)都优先使用显式类型。
- 仅类型导入时使用。
import type - 使用显式定义组件Props。当组件接收子元素时,使用
interface。PropsWithChildren - 将组件Props输入包裹在中。
Readonly<...> - 优先使用而非
unknown;通过类型守卫进行类型收窄。any - 避免使用非空断言符();优先使用可选链和显式守卫。
!
Imports
导入规则
- Prefer a alias for imports from
@/.src/ - Group imports: external libraries → internal modules → types. Separate groups with a blank line.
- All imports at the top of the file, never inline or inside try blocks.
- Prefer named exports for components and hooks. Avoid default exports; reserve them only for entrypoints, reducers, or interop files that require them.
- 优先使用别名导入
@/目录下的文件。src/ - 对导入进行分组:外部库 → 内部模块 → 类型。组与组之间用空行分隔。
- 所有导入都放在文件顶部,绝不要内联或放在try块内部。
- 组件和Hooks优先使用命名导出。避免默认导出;仅在入口文件、reducer或需要互操作的文件中使用默认导出。
JSX
JSX
- Prefer rendering variables over inline conditions. Derive named booleans before the return:
then
const showBanner = isLoaded && hasError && !isDismissed.showBanner ? <Banner /> : null - For a collection of flags, prefer or
[a, b, c].every(Boolean)over chaining[a, b, c].some(Boolean)or&&.|| - Never use nested ternaries. Use statements or rendering variables for anything beyond a single obvious ternary.
if - Prefer over
condition ? <Thing /> : null.condition && <Thing /> - Remove all instances of or
{' '}, use layout gap, margin, or padding instead.{" "} - Prefer destructuring to prop drilling. Stop drilling beyond two levels; lift state or use shared state instead.
- Prefer composition and small focused helpers over large boolean-heavy JSX branches.
- Keep JSX return values small; extract sub-trees into named components when a render block grows unwieldy.
- Prefer named handler functions over inlined arrow functions. Extract to a handler when the body reaches 3+ lines or is async:
rather than
const handleSubmit = async () => { ... }.onClick={async () => { ... }} - For fire-and-forget async handlers, use to avoid blocking:
void.void handleSubmit()
- 优先使用渲染变量而非内联条件。在return语句前推导命名布尔值:
,然后使用
const showBanner = isLoaded && hasError && !isDismissed。showBanner ? <Banner /> : null - 处理多个标志位时,优先使用或
[a, b, c].every(Boolean),而非链式使用[a, b, c].some(Boolean)或&&。|| - 绝不使用嵌套三元表达式。对于超出单个简单三元的场景,使用语句或渲染变量。
if - 优先使用而非
condition ? <Thing /> : null。condition && <Thing /> - 删除所有或
{' '}这类空格节点,改用布局间隙(gap)、外边距(margin)或内边距(padding)替代。{" "} - 优先使用解构而非Props透传。透传不要超过两层;否则应提升状态或使用共享状态。
- 优先使用组合和小型专用辅助组件,而非包含大量布尔判断的庞大JSX分支。
- 保持JSX返回值简洁;当渲染块变得臃肿时,将子树提取为命名组件。
- 优先使用命名处理函数而非内联箭头函数。当函数体达到3行及以上或为异步函数时,提取为处理函数:
而非
const handleSubmit = async () => { ... }。onClick={async () => { ... }} - 对于无需等待结果的异步处理函数,使用避免阻塞:
void。void handleSubmit()
Components and hooks
组件与Hooks
- Prefer named declarations.
export function - Destructure props in the function signature or immediately at the top of the function body. Do not thread chains.
props.foo - Prefer full variable names over abbreviations.
- Avoid comments that merely restate the code.
- Co-locate a custom hook with the component that owns it unless it is reused.
- Before creating a new component, enumerate what exists and confirm nothing covers the use case.
- Prefer existing page, hook, query, type, and layout patterns over new abstractions.
- 优先使用命名的声明。
export function - 在函数签名中或函数体顶部立即解构Props。不要使用这类链式调用。
props.foo - 优先使用完整变量名而非缩写。
- 避免仅重复代码内容的注释。
- 自定义Hook与其所属组件放在同一位置,除非该Hook会被复用。
- 创建新组件前,先列举现有组件,确认没有能覆盖当前用例的组件。
- 优先使用现有的页面、Hook、查询、类型和布局模式,而非创建新的抽象。
useEffect
, the last resort
useEffectuseEffect
,最后的选择
useEffectuseEffect| Problem | Prefer instead |
|---|---|
| Derived value from state or props | Compute inline during render; |
| Fetching data | TanStack Query |
| Resetting state when a prop changes | |
| Syncing with an external store | |
| DOM measurements before paint | |
| Triggering animations | |
useEffectNever use to sync one piece of state into another, that is always a sign the state model needs restructuring.
useEffectuseEffect| 问题 | 优先替代方案 |
|---|---|
| 从状态或Props派生值 | 在渲染时内联计算;若计算成本高则使用 |
| 获取数据 | TanStack Query |
| 当Props变化时重置状态 | 使用 |
| 与外部存储同步 | |
| 绘制前测量DOM | |
| 触发动画 | |
只有在处理真正需要清理的外部副作用时,才是合适的选择,比如事件监听器、WebSocket连接、第三方库初始化,或没有React抽象的浏览器API。如果不需要清理且没有涉及外部系统,那它几乎肯定不是正确的选择。
useEffect绝不要使用将一段状态同步到另一段状态,这永远是状态模型需要重构的信号。
useEffectState and data ownership
状态与数据所有权
- Prefer local state first.
- Lift state only when two or more components genuinely share it.
- Prefer Redux (or equivalent global store) for shared non-serializable state or when an existing slice already owns that UI state. Do not duplicate it in local state.
- Use TanStack Query for all server state. Keep factories in a dedicated
queryOptions(...)directory. Do not put fetch results in Redux.query/ - Keep server state separate from UI state, do not mix fetch results into UI slices.
- Keep HTTP access in a dedicated layer. Components should not call
api/directly.fetch - Keep contract types in . Do not invent frontend-only API contracts, align with the actual producer.
src/types/ - Prefer optimistic UI updates for mutations that are unlikely to fail. Roll back on error using the query client's /
onMutatepattern.onError
- 优先使用局部状态。
- 仅当两个或更多组件真正需要共享状态时,才提升状态。
- 对于共享的非可序列化状态,或当已有状态切片已经管理该UI状态时,优先使用Redux(或等效的全局存储)。不要在局部状态中重复存储。
- 所有服务端状态都使用TanStack Query管理。将工厂函数放在专门的
queryOptions(...)目录中。不要将请求结果存入Redux。query/ - 保持服务端状态与UI状态分离,不要将请求结果混入UI状态切片。
- 将HTTP请求放在专门的层中。组件不应直接调用
api/。fetch - 将契约类型放在目录中。不要自行定义仅前端使用的API契约,要与实际的后端生产者保持一致。
src/types/ - 对于不太可能失败的变更操作,优先使用乐观UI更新。通过查询客户端的/
onMutate模式在出错时回滚。onError
Routing
路由
- Validate search params explicitly at the route level.
- Derive loader dependencies from search/route state rather than component effects.
- Preload with route loaders when route state is required before render.
- Preserve route and layout structure unless the task intentionally changes it.
- 在路由层面显式验证搜索参数。
- 从搜索/路由状态派生加载器依赖,而非组件的副作用。
- 当路由状态需要在渲染前获取时,使用路由加载器进行预加载。
- 除非任务有意改变路由和布局结构,否则保持其原有结构。
Error and loading states
错误与加载状态
- Always handle loading, error, and empty states explicitly, avoid silent no-ops.
- Prefer graceful missing-data behavior over throwing or crashing.
- Surface errors at the boundary closest to the user action.
- 始终显式处理加载、错误和空状态,避免静默无操作。
- 优先使用优雅的缺失数据处理行为,而非抛出错误或崩溃。
- 在最接近用户操作的边界处展示错误信息。
Review checklist
评审检查清单
- JSX conditionals written as ternaries returning instead of
null?&& - No nested ternaries, replaced with rendering variables or statements?
if - Multi-flag conditions use /
[].every(Boolean)where readable?[].some(Boolean) - No stray or
{' '}spacer nodes?{" "} - Props destructured at the boundary? No drilling beyond two levels?
- Async handlers named and invoked with where fire-and-forget?
void - Inline arrow functions 3+ lines extracted to named handlers?
- Could any be replaced with derived state,
useEffect,useMemo,useLayoutEffect, or TanStack Query?useSyncExternalStore - Server state owned by TanStack Query, not local state or Redux?
- Mutations use optimistic updates where failure is unlikely?
- Shared state stays local unless a global store was actually warranted?
- Imports use and
@/where appropriate?import type - Loading, error, and empty states all handled?
- No frontend-only API contracts invented, types align with the actual producer?
- Change preserves the app's routing and data-loading model?
- Before creating a component, was the existing component inventory checked?
- JSX条件是否使用返回的三元表达式而非
null?&& - 是否没有嵌套三元表达式,改用渲染变量或语句?
if - 多标志位条件是否在可读性允许的情况下使用/
[].every(Boolean)?[].some(Boolean) - 是否没有多余的或
{' '}空格节点?{" "} - Props是否在边界处解构?透传是否不超过两层?
- 异步处理函数是否已命名,且在无需等待结果时使用调用?
void - 3行及以上的内联箭头函数是否已提取为命名处理函数?
- 是否有可以被派生状态、
useEffect、useMemo、useLayoutEffect或TanStack Query替代?useSyncExternalStore - 服务端状态是否由TanStack Query管理,而非局部状态或Redux?
- 变更操作是否在失败可能性低的情况下使用了乐观更新?
- 共享状态是否保持局部,除非确实需要全局存储?
- 导入是否在合适的地方使用了和
@/?import type - 是否处理了加载、错误和空状态?
- 是否没有自行定义仅前端使用的API契约,类型与实际生产者保持一致?
- 变更是否保留了应用的路由和数据加载模型?
- 创建组件前是否检查了现有组件库?",