clean-react-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clean Components

整洁组件

React components are functions with UI responsibilities. Keep each component focused, make props readable at the call site, and use composition when variants start multiplying.
React组件是承担UI职责的函数。保持每个组件专注单一职责,让调用处的props具备可读性,当变体开始增多时使用组合方式。

R1: Component Boundaries

R1:组件边界

A component should usually own one of these jobs:
  • A reusable primitive, like
    Button
    or
    TextField
  • A domain display, like
    InvoiceSummary
  • A workflow section, like
    ShippingAddressStep
  • A route or screen that coordinates smaller components
If a component fetches data, transforms it, manages several UI modes, and renders a large tree, split coordination from presentation.
一个组件通常应承担以下职责之一:
  • 可复用基础组件,如
    Button
    TextField
  • 领域展示组件,如
    InvoiceSummary
  • 流程环节组件,如
    ShippingAddressStep
  • 协调小型组件的路由或页面组件
如果一个组件既要获取数据、转换数据、管理多种UI模式,还要渲染大型组件树,那么请将协调逻辑与展示逻辑拆分。

R2, R12: Props

R2、R12:Props

tsx
// Bad - call site meaning is unclear
<UserCard user={user} true false "compact" />

// Good - one named concept
<UserCard user={user} variant="compact" showActions={false} />
  • Use object props for components; positional arguments do not apply to JSX.
  • Keep prop names domain-specific.
  • Avoid passing large objects when the component needs only a few fields, unless the object itself is the domain concept.
  • Avoid boolean prop combinations that create unrelated modes.
When modes are mutually exclusive, model props as a discriminated union so invalid combinations cannot be rendered.
tsx
// Bad - caller can pass contradictory props
type BannerProps = {
  isLoading?: boolean;
  error?: Error;
  message?: string;
};

// Good - each mode owns the props it needs
type BannerProps =
  | { status: "loading" }
  | { status: "error"; error: Error }
  | { status: "ready"; message: string };
tsx
// Bad - call site meaning is unclear
<UserCard user={user} true false "compact" />

// Good - one named concept
<UserCard user={user} variant="compact" showActions={false} />
  • 使用对象式props定义组件;位置参数不适用于JSX。
  • 保持prop名称与领域相关。
  • 除非对象本身是领域概念,否则当组件仅需要少数字段时,避免传递大对象。
  • 避免使用布尔值prop组合来创建不相关的模式。
当模式互斥时,将props建模为区分联合类型,这样就不会渲染无效的组合。
tsx
// Bad - caller can pass contradictory props
type BannerProps = {
  isLoading?: boolean;
  error?: Error;
  message?: string;
};

// Good - each mode owns the props it needs
type BannerProps =
  | { status: "loading" }
  | { status: "error"; error: Error }
  | { status: "ready"; message: string };

Composition Over Flags

组合优于标记

tsx
// Bad - one component owns unrelated modes
<Panel isModal isDismissible hasFooter />

// Good - structure communicates behavior
<Modal onDismiss={closeModal}>
  <PanelFooter actions={actions} />
</Modal>
Boolean props are fine for simple visual toggles. They are a smell when combinations create different components hiding behind one name.
tsx
// Bad - one component owns unrelated modes
<Panel isModal isDismissible hasFooter />

// Good - structure communicates behavior
<Modal onDismiss={closeModal}>
  <PanelFooter actions={actions} />
</Modal>
布尔值prop适用于简单的视觉切换。但当组合在一个名称下隐藏不同组件时,就需要警惕了。

R13: Conditional Rendering

R13:条件渲染

  • Prefer guard clauses for empty, loading, and error states.
  • Keep nested ternaries out of JSX.
  • Extract named render sections only when the name adds meaning.
  • Render explicit loading, error, empty, and success states when remote or nullable data can be in those states.
tsx
if (status === "loading") {
  return <Spinner />;
}

if (status === "error") {
  return <ErrorMessage error={error} />;
}

return <OrderDetails order={order} />;
  • 对于空状态、加载状态和错误状态,优先使用守卫语句。
  • 避免在JSX中使用嵌套三元表达式。
  • 仅当名称能增加含义时,才提取命名的渲染片段。
  • 当远程或可空数据可能处于加载、错误、空或成功状态时,要显式渲染这些状态。
tsx
if (status === "loading") {
  return <Spinner />;
}

if (status === "error") {
  return <ErrorMessage error={error} />;
}

return <OrderDetails order={order} />;

R14: Render Purity

R14:渲染纯度

Rendering should only calculate UI. Do not mutate data, write storage, start timers, subscribe, navigate, log analytics, or trigger network work during render. Put effects in event handlers,
useEffect
, data-fetching boundaries, or framework loaders/actions.
渲染应仅用于计算UI。不要在渲染期间修改数据、写入存储、启动定时器、订阅、导航、记录分析或触发网络请求。将副作用放在事件处理函数、
useEffect
、数据获取边界或框架加载器/动作中。

R9: Event Handlers

R9:事件处理函数

Event handlers should name the user action, not the element or mechanism. Keep the mutation path obvious at the call site.
tsx
// Bad - names the element, not the intent
function handleButtonClick() {
  setItems(items.filter((item) => item.id !== id));
}

// Good - names what the user did
function handleRemoveItem(id: string) {
  setItems((items) => items.filter((item) => item.id !== id));
}
Avoid embedding complex logic or multiple state updates directly in JSX props. Extract a named handler when the intent would not be clear inline.
tsx
// Bad - mutation path is buried in JSX
<button onClick={() => {
  setLoading(true);
  setError(null);
  submitOrder(cart);
}}>
  Place Order
</button>

// Good - intent is named, path is visible
<button onClick={handlePlaceOrder}>Place Order</button>
事件处理函数应命名用户的操作,而非元素或机制。在调用处保持修改路径清晰。
tsx
// Bad - names the element, not the intent
function handleButtonClick() {
  setItems(items.filter((item) => item.id !== id));
}

// Good - names what the user did
function handleRemoveItem(id: string) {
  setItems((items) => items.filter((item) => item.id !== id));
}
避免在JSX props中嵌入复杂逻辑或多个状态更新。当内联写法无法清晰表达意图时,提取命名的处理函数。
tsx
// Bad - mutation path is buried in JSX
<button onClick={() => {
  setLoading(true);
  setError(null);
  submitOrder(cart);
}}>
  Place Order
</button>

// Good - intent is named, path is visible
<button onClick={handlePlaceOrder}>Place Order</button>

Common Mistakes

常见错误

  • Creating a
    components/
    dumping ground with no domain ownership.
  • Extracting one-line components that hide simple JSX.
  • Passing
    className
    through every layer instead of creating clear layout boundaries.
  • Memoizing components before measuring a real render problem.
  • Mutating arrays or objects while preparing JSX.
  • Calling navigation, storage, analytics, or request code during render.
  • 创建无领域归属的
    components/
    目录,成为组件的“垃圾场”。
  • 提取仅一行代码的组件,隐藏了简单的JSX。
  • 层层传递
    className
    ,而非创建清晰的布局边界。
  • 在未测量实际渲染问题前就对组件进行 memoize 处理。
  • 在准备JSX时修改数组或对象。
  • 在渲染期间调用导航、存储、分析或请求代码。