clean-react-state

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clean State

整洁状态管理

State should have one owner and one source of truth. Store the minimal facts that change over time; derive everything else.
状态应只有一个所有者和单一数据源。仅存储随时间变化的最小必要信息;其余所有信息均通过派生得到。

R6: Local First

R6: 本地优先

Keep state close to where it is used. Lift state only when multiple components need to read or change it.
tsx
// Bad - parent owns state used by one child
function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return <DetailsPanel isOpen={isOpen} onOpenChange={setIsOpen} />;
}

// Good - child owns local UI state
function DetailsPanel() {
  const [isOpen, setIsOpen] = useState(false);
  // ...
}
将状态放在离使用它最近的位置。仅当多个组件需要读取或修改该状态时,才提升状态到父组件。
tsx
// 不良示例 - 父组件持有仅单个子组件使用的状态
function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return <DetailsPanel isOpen={isOpen} onOpenChange={setIsOpen} />;
}

// 良好示例 - 子组件持有本地UI状态
function DetailsPanel() {
  const [isOpen, setIsOpen] = useState(false);
  // ...
}

R5: Derived State

R5: 派生状态

Do not store values that can be calculated from props, state, or query data during render.
tsx
const completedItems = items.filter((item) => item.completed);
const hasCompletedItems = completedItems.length > 0;
Use
useMemo
only when the computation is expensive or referential stability matters.
不要存储可在渲染时通过props、state或查询数据计算得到的值。
tsx
const completedItems = items.filter((item) => item.completed);
const hasCompletedItems = completedItems.length > 0;
仅当计算成本高昂或引用稳定性很重要时,才使用
useMemo

Reducers

Reducers

Use
useReducer
when state transitions are related and event-like. Keep actions domain-specific.
tsx
type CartAction =
  | { type: "item-added"; item: CartItem }
  | { type: "item-removed"; itemId: string }
  | { type: "discount-applied"; code: string };
Avoid reducers that simply proxy
setState
with generic actions like
{ type: "set-value" }
.
当状态转换具有关联性且类似事件时,使用
useReducer
。保持操作与业务领域相关。
tsx
type CartAction =
  | { type: "item-added"; item: CartItem }
  | { type: "item-removed"; itemId: string }
  | { type: "discount-applied"; code: string };
避免使用仅通过
{ type: "set-value" }
这类通用操作代理
setState
的reducers。

Context

Context

  • Use context for values many descendants need.
  • Split read-heavy state from write actions when it prevents unnecessary rerenders.
  • Do not use context as a default replacement for props.
  • 当多个后代组件需要某个值时使用Context。
  • 当可以避免不必要的重渲染时,将只读状态与写入操作分离。
  • 不要将Context作为props的默认替代方案。

R7: Server State

R7: 服务端状态

Server data, cache lifetime, background refresh, optimistic updates, and request status are not ordinary local UI state. Prefer the project's data-fetching library when one exists.
Components that render server state should account for every user-visible state the data source can produce: loading, error, empty, and success. Do not collapse missing data, failed data, and still-loading data into the same fallback unless the product behavior intentionally treats them the same.
服务端数据、缓存生命周期、后台刷新、乐观更新和请求状态不属于普通的本地UI状态。如果项目已有数据获取库,优先使用该库。
渲染服务端状态的组件应考虑数据源可能产生的所有用户可见状态:加载中、错误、空数据和成功。除非产品行为有意将它们视为相同情况,否则不要将数据缺失、请求失败和仍在加载的情况合并为同一个回退状态。

Common Mistakes

常见错误

  • Mirroring props into state without a reset strategy.
  • Keeping both
    selectedId
    and
    selectedItem
    in state.
  • Using global stores for state that belongs to one screen.
  • Mixing remote cache state and transient UI state in one object.
  • Rendering nullable server data as if loading, error, and empty states were equivalent.
  • 在没有重置策略的情况下将props镜像到state中。
  • 同时在state中保存
    selectedId
    selectedItem
  • 为仅属于单个页面的状态使用全局存储。
  • 将远程缓存状态和临时UI状态混合在一个对象中。
  • 将可空的服务端数据视为加载中、错误和空状态等价的情况进行渲染。