react-state

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React State Management

React状态管理

Refactor component state for correctness, performance, and type safety in React + TypeScript.
在React + TypeScript中重构组件状态,确保正确性、性能与类型安全。

When to Use

使用场景

  • Refactoring or reviewing component state structure
  • Deciding between
    useState
    ,
    useRef
    , or derived values
  • Replacing boolean flags with finite state types
  • Choosing a third-party state management library
  • Applying TypeScript discriminated unions for type-safe state
  • Fixing unnecessary re-renders caused by state design
  • Eliminating prop drilling or Context overuse
  • 重构或审查组件状态结构
  • 决定使用
    useState
    useRef
    还是派生值
  • 用有限状态类型替代布尔标志
  • 选择第三方状态管理库
  • 应用TypeScript discriminated unions实现类型安全的状态
  • 修复由状态设计导致的不必要重渲染
  • 消除prop drilling或过度使用Context

Structuring State

状态结构化

Group Related State

分组相关状态

Group related data into a single object instead of maintaining multiple independent
useState
calls. This keeps updates consistent and reduces the chance of state getting out of sync.
tsx
// Avoid: separate variables for related data
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");

// Prefer: single object for related data
const [formData, setFormData] = useState({
  firstName: "",
  lastName: "",
  email: "",
});
将相关数据整合到单个对象中,而非维护多个独立的
useState
调用。这样能保持更新一致性,降低状态不同步的风险。
tsx
// 避免:为相关数据使用单独变量
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");

// 推荐:用单个对象存储相关数据
const [formData, setFormData] = useState({
  firstName: "",
  lastName: "",
  email: "",
});

Use Previous State When Updating Objects

更新对象时使用先前状态

Always use the callback form of the setter when the new state depends on the previous state. This avoids closure issues that lead to stale or lost updates.
tsx
// Avoid: spreading directly (risks stale closures)
setFormData({ ...formData, email: newEmail });

// Prefer: callback with previous state
setFormData((prev) => ({ ...prev, email: newEmail }));
当新状态依赖于先前状态时,始终使用setter的回调形式。这能避免闭包问题导致的陈旧或丢失更新。
tsx
// 避免:直接展开(存在闭包陈旧风险)
setFormData({ ...formData, email: newEmail });

// 推荐:带先前状态的回调
setFormData((prev) => ({ ...prev, email: newEmail }));

Derive State — Don't Duplicate It

派生状态——不要重复存储

Compute values directly during rendering instead of maintaining separate state variables for derived information.
tsx
// Avoid: duplicated state
const [hotels, setHotels] = useState<Hotel[]>([]);
const [selectedHotel, setSelectedHotel] = useState<Hotel | null>(null);

// Prefer: store only the ID, derive the object
const [hotels, setHotels] = useState<Hotel[]>([]);
const [selectedHotelId, setSelectedHotelId] = useState<string | null>(null);
const selectedHotel = hotels.find((h) => h.id === selectedHotelId) ?? null;
Principle: If a value can be computed from existing state or props, derive it during rendering rather than storing it separately.
在渲染期间直接计算值,而非为派生信息维护单独的状态变量。
tsx
// 避免:重复状态
const [hotels, setHotels] = useState<Hotel[]>([]);
const [selectedHotel, setSelectedHotel] = useState<Hotel | null>(null);

// 推荐:仅存储ID,派生对象
const [hotels, setHotels] = useState<Hotel[]>([]);
const [selectedHotelId, setSelectedHotelId] = useState<string | null>(null);
const selectedHotel = hotels.find((h) => h.id === selectedHotelId) ?? null;
原则: 如果某个值可以通过现有状态或props计算得出,应在渲染期间派生它,而非单独存储。

When NOT to Use
useState

何时不使用
useState

Avoid
useState
for:
ScenarioUse Instead
Static values that never change
const
or module-level variable
Values computable from existing state/propsDerive inline during render
Values that don't need to trigger re-renders
useRef
避免在以下场景使用
useState
场景替代方案
永不改变的静态值
const
或模块级变量
可通过现有状态/props计算的值渲染时内联派生
无需触发重渲染的值
useRef

Use
useRef
for Non-Rendering Values

使用
useRef
存储非渲染值

For values that update frequently but should not cause re-renders, use
useRef
.
Scroll position tracking:
tsx
const scrollPosition = useRef(0);

useEffect(() => {
  const handleScroll = () => {
    scrollPosition.current = window.scrollY;
  };
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);
Timer IDs:
tsx
const timerId = useRef<ReturnType<typeof setTimeout> | null>(null);

const startTimer = () => {
  timerId.current = setTimeout(() => {
    // ...
  }, 1000);
};

const stopTimer = () => {
  if (timerId.current) clearTimeout(timerId.current);
};
对于频繁更新但不应触发重渲染的值,使用
useRef
滚动位置跟踪:
tsx
const scrollPosition = useRef(0);

useEffect(() => {
  const handleScroll = () => {
    scrollPosition.current = window.scrollY;
  };
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);
计时器ID:
tsx
const timerId = useRef<ReturnType<typeof setTimeout> | null>(null);

const startTimer = () => {
  timerId.current = setTimeout(() => {
    // ...
  }, 1000);
};

const stopTimer = () => {
  if (timerId.current) clearTimeout(timerId.current);
};

TypeScript Patterns for Type-Safe State

TypeScript类型安全状态模式

Finite States

有限状态

Represent a limited, predefined set of possible states to simplify state tracking and prevent impossible combinations.
tsx
type RequestStatus = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<RequestStatus>("idle");
用有限的、预定义的状态集合来简化状态跟踪,防止出现不可能的组合。
tsx
type RequestStatus = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<RequestStatus>("idle");

Discriminated Unions

Discriminated Unions

Use a shared
status
property to differentiate state shapes, ensuring type safety and consistent state representation.
tsx
type RequestState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: Data }
  | { status: "error"; error: Error };

const [state, setState] = useState<RequestState>({ status: "idle" });

// TypeScript narrows the type automatically
if (state.status === "success") {
  console.log(state.data); // ✓ data is available
}
使用共享的
status
属性区分状态结构,确保类型安全与状态表示的一致性。
tsx
type RequestState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: Data }
  | { status: "error"; error: Error };

const [state, setState] = useState<RequestState>({ status: "idle" });

// TypeScript会自动缩小类型范围
if (state.status === "success") {
  console.log(state.data); // ✓ 可以访问data
}

Type States

类型状态

Enforce data consistency by ensuring certain properties only exist in specific state conditions. This prevents runtime errors and provides compile-time type checking.
tsx
type FormState =
  | { submitted: false; values: FormValues }
  | { submitted: true; values: FormValues; response: ServerResponse };
通过确保某些属性仅在特定状态条件下存在,强制数据一致性。这能防止运行时错误,并提供编译时类型检查。
tsx
type FormState =
  | { submitted: false; values: FormValues }
  | { submitted: true; values: FormValues; response: ServerResponse };

Choosing a Third-Party State Management Library

选择第三方状态管理库

When You Need One

何时需要使用

Look for these signs:
  • Over-relying on React Context for frequently changing values
  • Prop drilling across many component layers
  • Scattered state logic that's hard to follow
  • State synchronization issues between components
  • Frequent unnecessary re-renders
留意以下迹象:
  • 过度依赖React Context处理频繁变化的值
  • 跨多层组件的prop drilling
  • 分散的状态逻辑难以追踪
  • 组件间的状态同步问题
  • 频繁的不必要重渲染

Two Main Approaches

两种主要方案

ApproachCharacteristicsBest For
Store-based (e.g. Zustand, Redux)Centralized state, controlled transitions via actions/events, indirect mutationComplex logic, strict state control
Atomic / Signal-based (e.g. Jotai, Recoil, Signals)Decentralized atoms, reactive subscriptions, updatable from anywhereData that changes freely from external sources, fine-grained reactivity
方案特性适用场景
基于Store(如Zustand、Redux)集中式状态,通过actions/events控制转换,间接修改状态复杂逻辑、严格的状态控制
原子/基于Signal(如Jotai、Recoil、Signals)去中心化原子,响应式订阅,可在任意位置更新来自外部源自由变化的数据(如实时更新、限时促销)、细粒度响应性

Decision Guide

决策指南

  • Complex logic with controlled transitions → Store-based. Prevents arbitrary changes; state flows through defined actions.
  • Data that changes freely from external sources (e.g. real-time updates, flash sales) → Atomic/signal-based. Allows updating from anywhere and combining/deriving state from multiple sources.
  • 带受控转换的复杂逻辑 → 基于Store。防止任意修改;状态通过定义好的actions流转。
  • 来自外部源自由变化的数据(如实时更新、限时促销)→ 原子/基于Signal。允许在任意位置更新,并可从多个源组合/派生状态。

Performance: Fine-Grained Subscriptions

性能:细粒度订阅

Use selectors (e.g.
useSelector
, Zustand selectors) so components only re-render when the specific slice of state they consume changes, rather than on every store update.
tsx
// Only re-renders when `count` changes, not the entire store
const count = useStore((state) => state.count);
使用选择器(如
useSelector
、Zustand选择器),让组件仅在其使用的特定状态切片变化时重渲染,而非在每次Store更新时都重渲染。
tsx
// 仅在`count`变化时重渲染,而非整个Store更新时
const count = useStore((state) => state.count);

Common Anti-Patterns

常见反模式

Anti-PatternProblemFix
const [isLoading, setIsLoading] = useState(false)
+
const [isError, setIsError] = useState(false)
Impossible states (
isLoading && isError
can both be true)
Use a single
status
union:
'idle' | 'loading' | 'success' | 'error'
useEffect
that syncs state A into state B
Creates render cascades and stale dataDerive B from A inline during render
useState
for a value only read in event handlers
Causes re-renders on every update with no visual changeUse
useRef
Storing full objects when only an ID is neededStale references when source array updatesStore the ID, derive the object via
.find()
useContext
for high-frequency updates
All consumers re-render on every changeUse a store with selectors or split into separate contexts
反模式问题修复方案
const [isLoading, setIsLoading] = useState(false)
+
const [isError, setIsError] = useState(false)
出现不可能的状态(
isLoading && isError
可能同时为true)
使用单个
status
联合类型:
'idle' | 'loading' | 'success' | 'error'
useEffect
同步状态A到状态B
导致渲染级联和陈旧数据渲染时内联从A派生B
为仅在事件处理程序中读取的值使用
useState
每次更新都会触发重渲染,但无视觉变化使用
useRef
仅需ID时存储完整对象源数组更新时出现陈旧引用存储ID,通过
.find()
派生对象
为高频更新使用
useContext
所有消费者在每次变化时都会重渲染使用带选择器的Store,或拆分为多个Context

Procedure

流程

When refactoring state in a component:
  1. Inventory all state variables. List every
    useState
    call and what triggers each update.
  2. Group related state. Merge variables that always update together into a single object.
  3. Eliminate derived state. Remove any state that can be computed from other state or props; compute it inline.
  4. Swap non-rendering values to refs. Timer IDs, scroll positions, previous values — move them to
    useRef
    .
  5. Check for impossible states. If multiple booleans control a single concept, replace them with a status union type.
  6. Use discriminated unions. For multi-shape state, add a
    status
    discriminator and let TypeScript narrow types.
  7. Verify update patterns. Ensure all object/array updates use the callback form
    setState(prev => ...)
    .
  8. Evaluate library need. If prop drilling, context overuse, or sync issues persist, choose a store-based or atomic library per the decision guide above.
重构组件状态时:
  1. 盘点所有状态变量。列出每个
    useState
    调用及触发更新的原因。
  2. 分组相关状态。将总是一起更新的变量合并到单个对象中。
  3. 消除派生状态。移除任何可通过其他状态或props计算的状态;内联计算。
  4. 将非渲染值替换为refs。计时器ID、滚动位置、先前值——移至
    useRef
  5. 检查不可能的状态。如果多个布尔值控制同一个概念,用状态联合类型替换它们。
  6. 使用Discriminated Unions。对于多结构状态,添加
    status
    区分符,让TypeScript缩小类型范围。
  7. 验证更新模式。确保所有对象/数组更新都使用回调形式
    setState(prev => ...)
  8. 评估是否需要库。如果prop drilling、过度使用Context或同步问题仍然存在,根据上述决策指南选择基于Store或原子的库。