react-state
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact 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, or derived valuesuseRef - 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 calls. This keeps updates consistent and reduces the chance of state getting out of sync.
useStatetsx
// 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: "",
});将相关数据整合到单个对象中,而非维护多个独立的调用。这样能保持更新一致性,降低状态不同步的风险。
useStatetsx
// 避免:为相关数据使用单独变量
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何时不使用useState
useStateAvoid for:
useState| Scenario | Use Instead |
|---|---|
| Static values that never change | |
| Values computable from existing state/props | Derive inline during render |
| Values that don't need to trigger re-renders | |
避免在以下场景使用:
useState| 场景 | 替代方案 |
|---|---|
| 永不改变的静态值 | |
| 可通过现有状态/props计算的值 | 渲染时内联派生 |
| 无需触发重渲染的值 | |
Use useRef
for Non-Rendering Values
useRef使用useRef
存储非渲染值
useRefFor values that update frequently but should not cause re-renders, use .
useRefScroll 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 property to differentiate state shapes, ensuring type safety and consistent state representation.
statustsx
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
}使用共享的属性区分状态结构,确保类型安全与状态表示的一致性。
statustsx
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
两种主要方案
| Approach | Characteristics | Best For |
|---|---|---|
| Store-based (e.g. Zustand, Redux) | Centralized state, controlled transitions via actions/events, indirect mutation | Complex logic, strict state control |
| Atomic / Signal-based (e.g. Jotai, Recoil, Signals) | Decentralized atoms, reactive subscriptions, updatable from anywhere | Data 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. , Zustand selectors) so components only re-render when the specific slice of state they consume changes, rather than on every store update.
useSelectortsx
// Only re-renders when `count` changes, not the entire store
const count = useStore((state) => state.count);使用选择器(如、Zustand选择器),让组件仅在其使用的特定状态切片变化时重渲染,而非在每次Store更新时都重渲染。
useSelectortsx
// 仅在`count`变化时重渲染,而非整个Store更新时
const count = useStore((state) => state.count);Common Anti-Patterns
常见反模式
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Impossible states ( | Use a single |
| Creates render cascades and stale data | Derive B from A inline during render |
| Causes re-renders on every update with no visual change | Use |
| Storing full objects when only an ID is needed | Stale references when source array updates | Store the ID, derive the object via |
| All consumers re-render on every change | Use a store with selectors or split into separate contexts |
| 反模式 | 问题 | 修复方案 |
|---|---|---|
| 出现不可能的状态( | 使用单个 |
用 | 导致渲染级联和陈旧数据 | 渲染时内联从A派生B |
为仅在事件处理程序中读取的值使用 | 每次更新都会触发重渲染,但无视觉变化 | 使用 |
| 仅需ID时存储完整对象 | 源数组更新时出现陈旧引用 | 存储ID,通过 |
为高频更新使用 | 所有消费者在每次变化时都会重渲染 | 使用带选择器的Store,或拆分为多个Context |
Procedure
流程
When refactoring state in a component:
- Inventory all state variables. List every call and what triggers each update.
useState - Group related state. Merge variables that always update together into a single object.
- Eliminate derived state. Remove any state that can be computed from other state or props; compute it inline.
- Swap non-rendering values to refs. Timer IDs, scroll positions, previous values — move them to .
useRef - Check for impossible states. If multiple booleans control a single concept, replace them with a status union type.
- Use discriminated unions. For multi-shape state, add a discriminator and let TypeScript narrow types.
status - Verify update patterns. Ensure all object/array updates use the callback form .
setState(prev => ...) - Evaluate library need. If prop drilling, context overuse, or sync issues persist, choose a store-based or atomic library per the decision guide above.
重构组件状态时:
- 盘点所有状态变量。列出每个调用及触发更新的原因。
useState - 分组相关状态。将总是一起更新的变量合并到单个对象中。
- 消除派生状态。移除任何可通过其他状态或props计算的状态;内联计算。
- 将非渲染值替换为refs。计时器ID、滚动位置、先前值——移至。
useRef - 检查不可能的状态。如果多个布尔值控制同一个概念,用状态联合类型替换它们。
- 使用Discriminated Unions。对于多结构状态,添加区分符,让TypeScript缩小类型范围。
status - 验证更新模式。确保所有对象/数组更新都使用回调形式。
setState(prev => ...) - 评估是否需要库。如果prop drilling、过度使用Context或同步问题仍然存在,根据上述决策指南选择基于Store或原子的库。