state-management-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

State Management Patterns

Web应用状态管理模式

Overview

概述

State is data that changes over time. Where and how you manage state significantly impacts application architecture, performance, and complexity.
状态是随时间变化的数据。状态的存储位置和管理方式会显著影响应用的架构、性能与复杂度。

Types of State

状态类型

TypeExamplesCharacteristics
UI StateModal open, tab selected, form inputLocal, ephemeral
Server StateUser data, products, postsRemote, cached, async
URL StatePage, filters, search queryShareable, bookmarkable
Browser StatelocalStorage, sessionStorage, cookiesPersistent, limited
Application StateAuth, theme, user preferencesGlobal, session-scoped
类型示例特性
UI State模态框开启、选中标签、表单输入本地存储、临时有效
Server State用户数据、商品信息、帖子内容远程存储、可缓存、异步获取
URL State页码、筛选条件、搜索关键词可分享、可收藏
Browser StatelocalStorage、sessionStorage、Cookies持久化、容量有限
Application State认证状态、主题设置、用户偏好全局共享、会话级有效

Client State vs Server State

客户端状态 vs 服务端状态

Client State

客户端状态

Data that exists only in the browser:
javascript
// UI state - component-local
const [isOpen, setIsOpen] = useState(false);

// Application state - global
const [theme, setTheme] = useContext(ThemeContext);
仅存在于浏览器中的数据:
javascript
// UI state - component-local
const [isOpen, setIsOpen] = useState(false);

// Application state - global
const [theme, setTheme] = useContext(ThemeContext);

Server State

服务端状态

Data fetched from a server:
javascript
// Different characteristics
const { data, loading, error } = useQuery('/api/products');

// Server state is:
// - Potentially stale
// - Requires caching strategy
// - Async (loading, error states)
// - Shared across components
从服务器获取的数据:
javascript
// Different characteristics
const { data, loading, error } = useQuery('/api/products');

// Server state is:
// - Potentially stale
// - Requires caching strategy
// - Async (loading, error states)
// - Shared across components

State Colocation

状态就近原则

Place state as close as possible to where it's used.
将状态存储在离使用它的位置最近的地方。

Component State (Local)

组件状态(本地)

jsx
// Good: State used only in this component
function Toggle() {
  const [on, setOn] = useState(false);
  return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>;
}
jsx
// Good: State used only in this component
function Toggle() {
  const [on, setOn] = useState(false);
  return <button onClick={() => setOn(!on)}>{on ? 'On' : 'Off'}</button>;
}

Lifted State (Shared by Siblings)

提升状态(兄弟组件共享)

jsx
// Good: State shared between siblings, lifted to parent
function Form() {
  const [value, setValue] = useState('');
  return (
    <>
      <Input value={value} onChange={setValue} />
      <Preview value={value} />
    </>
  );
}
jsx
// Good: State shared between siblings, lifted to parent
function Form() {
  const [value, setValue] = useState('');
  return (
    <>
      <Input value={value} onChange={setValue} />
      <Preview value={value} />
    </>
  );
}

Context (Deep Tree Access)

Context(深层组件访问)

jsx
// Good: State needed by many components at different levels
<ThemeProvider value={theme}>
  <App />  {/* Any child can access theme */}
</ThemeProvider>
jsx
// Good: State needed by many components at different levels
<ThemeProvider value={theme}>
  <App />  {/* Any child can access theme */}
</ThemeProvider>

Global State (Application-Wide)

全局状态(全应用共享)

javascript
// Use sparingly for truly global concerns:
// - Authentication state
// - Feature flags
// - User preferences
javascript
// Use sparingly for truly global concerns:
// - Authentication state
// - Feature flags
// - User preferences

Server State Management

服务端状态管理

The Problem

存在的问题

javascript
// Without caching
function ProductList() {
  const [products, setProducts] = useState([]);
  
  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setProducts);
  }, []);
  // Every mount = new request
  // No loading state handling
  // No error handling
  // No caching
}
javascript
// Without caching
function ProductList() {
  const [products, setProducts] = useState([]);
  
  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setProducts);
  }, []);
  // Every mount = new request
  // No loading state handling
  // No error handling
  // No caching
}

Server State Libraries

服务端状态管理库

Libraries like TanStack Query, SWR, Apollo handle:
javascript
// With TanStack Query
function ProductList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
    staleTime: 60000,  // Fresh for 1 minute
  });
}
Benefits:
  • Automatic caching and deduplication
  • Background refetching
  • Loading and error states
  • Cache invalidation
  • Optimistic updates
TanStack Query、SWR、Apollo等库可以处理这些问题:
javascript
// With TanStack Query
function ProductList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
    staleTime: 60000,  // Fresh for 1 minute
  });
}
优势:
  • 自动缓存与请求去重
  • 后台自动重新获取
  • 内置加载与错误状态处理
  • 缓存失效机制
  • 乐观更新

URL State

URL状态

State that should survive refresh and be shareable.
可在页面刷新后保留、且可分享的状态。

When to Use URL State

适用场景

  • Pagination:
    /products?page=2
  • Filters:
    /products?category=shoes&size=10
  • Search:
    /search?q=query
  • Tabs/views:
    /dashboard?view=analytics
  • Modal content:
    /products/123
    with modal
  • 分页:
    /products?page=2
  • 筛选:
    /products?category=shoes&size=10
  • 搜索:
    /search?q=query
  • 标签页/视图切换:
    /dashboard?view=analytics
  • 模态框内容:
    /products/123
    (配合模态框展示)

Managing URL State

URL状态管理

javascript
// Read from URL
const searchParams = useSearchParams();
const page = searchParams.get('page') || '1';

// Update URL
function setPage(newPage) {
  const params = new URLSearchParams(searchParams);
  params.set('page', newPage);
  router.push(`?${params.toString()}`);
}
javascript
// Read from URL
const searchParams = useSearchParams();
const page = searchParams.get('page') || '1';

// Update URL
function setPage(newPage) {
  const params = new URLSearchParams(searchParams);
  params.set('page', newPage);
  router.push(`?${params.toString()}`);
}

URL vs Local State

URL状态 vs 本地状态

StateURLLocal
Current search query
Search input (typing)
Selected filters
Filter dropdown open
Current page
Scroll position
状态场景URL本地
当前搜索关键词
输入中的搜索内容
已选中的筛选条件
筛选下拉框展开状态
当前页码
滚动位置

Browser Storage

浏览器存储

Comparison

存储方式对比

StorageSizePersistenceAccess
localStorage~5MBUntil clearedSame origin
sessionStorage~5MBTab sessionSame tab
Cookies~4KBConfigurableSent to server
IndexedDBLargeUntil clearedSame origin
存储方式容量持久化访问权限
localStorage~5MB手动清除前一直保留同源访问
sessionStorage~5MB标签页会话期间有效同标签页访问
Cookies~4KB可配置有效期会发送至服务器
IndexedDB大容量手动清除前一直保留同源访问

Common Patterns

常见使用模式

javascript
// Persist user preferences
const theme = localStorage.getItem('theme') || 'light';

// Session-specific state
const draftId = sessionStorage.getItem('draft-id');

// Auth tokens (consider security implications)
// HttpOnly cookies preferred for tokens
javascript
// Persist user preferences
const theme = localStorage.getItem('theme') || 'light';

// Session-specific state
const draftId = sessionStorage.getItem('draft-id');

// Auth tokens (consider security implications)
// HttpOnly cookies preferred for tokens

Server Components and State

服务端组件与状态

React Server Components

React Server Components

jsx
// Server Component - no useState, no client-side state
async function ProductPage({ id }) {
  const product = await db.products.findById(id);  // Direct DB access
  return <ProductDetails product={product} />;
}

// Client Component - for interactivity
'use client';
function AddToCart({ productId }) {
  const [quantity, setQuantity] = useState(1);
  return <button onClick={() => addToCart(productId, quantity)}>Add</button>;
}
jsx
// Server Component - no useState, no client-side state
async function ProductPage({ id }) {
  const product = await db.products.findById(id);  // Direct DB access
  return <ProductDetails product={product} />;
}

// Client Component - for interactivity
'use client';
function AddToCart({ productId }) {
  const [quantity, setQuantity] = useState(1);
  return <button onClick={() => addToCart(productId, quantity)}>Add</button>;
}

Server State is Simpler

服务端状态更简洁

Server Component Approach:
  Database → Server Component → HTML to client
  (No loading states needed, data already there)

Client Fetch Approach:
  Server Component → Client Component → Fetch → Loading → Data → Render
  (More complexity, but necessary for interactivity)
Server Component Approach:
  Database → Server Component → HTML to client
  (No loading states needed, data already there)

Client Fetch Approach:
  Server Component → Client Component → Fetch → Loading → Data → Render
  (More complexity, but necessary for interactivity)

Caching Strategies

缓存策略

Cache-First (Stale-While-Revalidate)

缓存优先(Stale-While-Revalidate)

javascript
// Show cached data immediately, refetch in background
const { data } = useQuery({
  queryKey: ['products'],
  staleTime: 60000,      // Cached data considered fresh for 1 min
  cacheTime: 300000,     // Keep in cache for 5 min
});
javascript
// Show cached data immediately, refetch in background
const { data } = useQuery({
  queryKey: ['products'],
  staleTime: 60000,      // Cached data considered fresh for 1 min
  cacheTime: 300000,     // Keep in cache for 5 min
});

Network-First

网络优先

javascript
// Always fetch fresh, fall back to cache
const { data } = useQuery({
  queryKey: ['products'],
  staleTime: 0,          // Always refetch
  networkMode: 'always',
});
javascript
// Always fetch fresh, fall back to cache
const { data } = useQuery({
  queryKey: ['products'],
  staleTime: 0,          // Always refetch
  networkMode: 'always',
});

Cache Invalidation

缓存失效

javascript
// After mutation, invalidate related queries
const mutation = useMutation({
  mutationFn: updateProduct,
  onSuccess: () => {
    queryClient.invalidateQueries(['products']);
  },
});
javascript
// After mutation, invalidate related queries
const mutation = useMutation({
  mutationFn: updateProduct,
  onSuccess: () => {
    queryClient.invalidateQueries(['products']);
  },
});

State Machine Patterns

状态机模式

For complex state transitions:
javascript
// Instead of multiple booleans
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// Problem: Can be in impossible states (loading AND error)

// Use a state machine
const [state, setState] = useState('idle');
// 'idle' | 'loading' | 'success' | 'error'
// Impossible to be in multiple states
适用于复杂的状态转换场景:
javascript
// Instead of multiple booleans
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// Problem: Can be in impossible states (loading AND error)

// Use a state machine
const [state, setState] = useState('idle');
// 'idle' | 'loading' | 'success' | 'error'
// Impossible to be in multiple states

Anti-Patterns

反模式

1. Prop Drilling

1. 属性透传

jsx
// Bad: Passing through many levels
<App user={user}>
  <Layout user={user}>
    <Page user={user}>
      <Header user={user}>
        <Avatar user={user} />

// Good: Use context for deep access
<UserContext.Provider value={user}>
  <App />
jsx
// Bad: Passing through many levels
<App user={user}>
  <Layout user={user}>
    <Page user={user}>
      <Header user={user}>
        <Avatar user={user} />

// Good: Use context for deep access
<UserContext.Provider value={user}>
  <App />

2. Global State for Local Needs

2. 用全局状态存储本地数据

jsx
// Bad: Everything in global store
dispatch({ type: 'SET_MODAL_OPEN', payload: true });

// Good: Local state for UI
const [isOpen, setIsOpen] = useState(false);
jsx
// Bad: Everything in global store
dispatch({ type: 'SET_MODAL_OPEN', payload: true });

// Good: Local state for UI
const [isOpen, setIsOpen] = useState(false);

3. Duplicating Server State

3. 复制服务端状态到本地

jsx
// Bad: Copying server state to local state
const [products, setProducts] = useState([]);
useEffect(() => {
  fetch('/api/products').then(r => r.json()).then(setProducts);
}, []);

// Good: Server state library handles caching
const { data: products } = useQuery(['products'], fetchProducts);
jsx
// Bad: Copying server state to local state
const [products, setProducts] = useState([]);
useEffect(() => {
  fetch('/api/products').then(r => r.json()).then(setProducts);
}, []);

// Good: Server state library handles caching
const { data: products } = useQuery(['products'], fetchProducts);

4. Not Considering URL State

4. 忽略URL状态

jsx
// Bad: Filter state lost on refresh
const [filters, setFilters] = useState({ category: 'all' });

// Good: Filters in URL
const filters = Object.fromEntries(useSearchParams());
jsx
// Bad: Filter state lost on refresh
const [filters, setFilters] = useState({ category: 'all' });

// Good: Filters in URL
const filters = Object.fromEntries(useSearchParams());

Decision Framework

决策框架

Is this UI state (ephemeral, local)?
├── Yes → useState in component
└── No → Is it shared between siblings?
          ├── Yes → Lift state to parent
          └── No → Is it needed deep in tree?
                    ├── Yes → Context or global state
                    └── No → Is it from a server?
                              ├── Yes → Server state library
                              └── No → Should it survive refresh?
                                        ├── Yes → URL or localStorage
                                        └── No → Component state

是否为UI状态(临时、本地)?
├── 是 → 在组件中使用useState
└── 否 → 是否为兄弟组件共享状态?
          ├── 是 → 将状态提升至父组件
          └── 否 → 是否需要在组件树深层访问?
                    ├── 是 → 使用Context或全局状态
                    └── 否 → 是否来自服务器?
                              ├── 是 → 使用服务端状态管理库
                              └── 否 → 是否需要在刷新后保留?
                                        ├── 是 → 使用URL或localStorage
                                        └── 否 → 使用组件状态

Deep Dive: Understanding State From First Principles

深度剖析:从底层原理理解状态

What Is State, Really?

什么是状态?

State is any data that changes over time and affects what the user sees or can do. Understanding this precisely helps you manage it correctly:
javascript
// STATE: Data that changes and triggers UI updates

// Example 1: Counter
let count = 0;  // ← This is state (changes on click)
button.onclick = () => {
  count++;
  display.textContent = count;  // Must update UI manually!
};

// Example 2: Form input
let inputValue = '';  // ← This is state (changes on type)

// Example 3: Fetched data
let products = null;  // ← This is state (changes on fetch complete)


// NOT STATE: Static data that never changes
const API_URL = 'https://api.example.com';  // Configuration, not state
const TAX_RATE = 0.08;  // Constant, not state


// THE PROBLEM: Keeping UI in sync with state
// Without a framework, YOU must update the DOM manually every time state changes
// Frameworks automate this: state change → automatic re-render
状态是随时间变化、并影响用户所见或操作的所有数据。精准理解这一点有助于正确管理状态:
javascript
// STATE: Data that changes and triggers UI updates

// Example 1: Counter
let count = 0;  // ← This is state (changes on click)
button.onclick = () => {
  count++;
  display.textContent = count;  // Must update UI manually!
};

// Example 2: Form input
let inputValue = '';  // ← This is state (changes on type)

// Example 3: Fetched data
let products = null;  // ← This is state (changes on fetch complete)


// NOT STATE: Static data that never changes
const API_URL = 'https://api.example.com';  // Configuration, not state
const TAX_RATE = 0.08;  // Constant, not state


// THE PROBLEM: Keeping UI in sync with state
// Without a framework, YOU must update the DOM manually every time state changes
// Frameworks automate this: state change → automatic re-render

The Reactivity Problem

响应式难题

Why do frameworks exist? Because keeping UI in sync with state is HARD:
javascript
// VANILLA JAVASCRIPT - Manual synchronization

const state = {
  user: null,
  posts: [],
  selectedPost: null,
  isLoading: false,
};

// Every state change requires manual DOM updates
function setUser(user) {
  state.user = user;
  
  // Now manually update every place user is displayed:
  document.getElementById('user-name').textContent = user.name;
  document.getElementById('user-avatar').src = user.avatar;
  document.getElementById('welcome-message').textContent = `Welcome, ${user.name}`;
  // ... and any other places
  // Miss one? Bug. UI and state are out of sync.
}

function setPosts(posts) {
  state.posts = posts;
  
  // Clear and rebuild entire list
  const container = document.getElementById('posts');
  container.innerHTML = '';
  posts.forEach(post => {
    const el = document.createElement('div');
    el.textContent = post.title;
    el.onclick = () => setSelectedPost(post);
    container.appendChild(el);
  });
}

// 100 state variables = 100 manual update functions
// Complex UIs become unmanageable
The framework solution:
jsx
// REACT - Declarative UI based on state

function App() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  
  // Describe WHAT UI should look like given state
  // React figures out HOW to update DOM
  return (
    <div>
      {user && <span>Welcome, {user.name}</span>}
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

// Change state → React re-renders → DOM automatically updated
// You never touch the DOM directly
为什么需要前端框架?因为保持UI与状态同步非常困难:
javascript
// VANILLA JAVASCRIPT - Manual synchronization

const state = {
  user: null,
  posts: [],
  selectedPost: null,
  isLoading: false,
};

// Every state change requires manual DOM updates
function setUser(user) {
  state.user = user;
  
  // Now manually update every place user is displayed:
  document.getElementById('user-name').textContent = user.name;
  document.getElementById('user-avatar').src = user.avatar;
  document.getElementById('welcome-message').textContent = `Welcome, ${user.name}`;
  // ... and any other places
  // Miss one? Bug. UI and state are out of sync.
}

function setPosts(posts) {
  state.posts = posts;
  
  // Clear and rebuild entire list
  const container = document.getElementById('posts');
  container.innerHTML = '';
  posts.forEach(post => {
    const el = document.createElement('div');
    el.textContent = post.title;
    el.onclick = () => setSelectedPost(post);
    container.appendChild(el);
  });
}

// 100 state variables = 100 manual update functions
// Complex UIs become unmanageable
框架的解决方案:
jsx
// REACT - Declarative UI based on state

function App() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  
  // Describe WHAT UI should look like given state
  // React figures out HOW to update DOM
  return (
    <div>
      {user && <span>Welcome, {user.name}</span>}
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

// Change state → React re-renders → DOM automatically updated
// You never touch the DOM directly

Memory Model: Where State Lives

内存模型:状态存储位置

Understanding memory helps you understand state:
BROWSER MEMORY MODEL:

┌─────────────────────────────────────────────────────────────────┐
│                        JavaScript Heap                           │
│                                                                  │
│  ┌──────────────────────┐  ┌──────────────────────┐             │
│  │   Component State    │  │   Closure Scopes     │             │
│  │   (useState, etc.)   │  │   (functions, refs)  │             │
│  └──────────────────────┘  └──────────────────────┘             │
│                                                                  │
│  ┌──────────────────────┐  ┌──────────────────────┐             │
│  │   Module Scope       │  │   Global Variables   │             │
│  │   (imports, consts)  │  │   (window.*)         │             │
│  └──────────────────────┘  └──────────────────────┘             │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        Browser Storage                           │
│                                                                  │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌───────────┐ │
│  │localStorage│  │sessionStor.│  │  Cookies   │  │ IndexedDB │ │
│  │  (~5MB)    │  │   (~5MB)   │  │   (~4KB)   │  │  (large)  │ │
│  └────────────┘  └────────────┘  └────────────┘  └───────────┘ │
└─────────────────────────────────────────────────────────────────┘

JavaScript Heap: Cleared on page reload
Browser Storage: Persists across reloads (until cleared)
State lifecycle in SPAs:
javascript
// 1. Page loads, JavaScript executes
// Heap is empty

// 2. Components mount, state initialized
const [count, setCount] = useState(0);  // Allocated in heap

// 3. User interacts, state changes
setCount(1);  // New value in heap, old garbage collected

// 4. Navigation within SPA
// Heap PERSISTS - state survives navigation!

// 5. Page reload or close
// Heap DESTROYED - all in-memory state lost
// Only browser storage survives
理解内存有助于理解状态:
BROWSER MEMORY MODEL:

┌─────────────────────────────────────────────────────────────────┐
│                        JavaScript Heap                           │
│                                                                  │
│  ┌──────────────────────┐  ┌──────────────────────┐             │
│  │   Component State    │  │   Closure Scopes     │             │
│  │   (useState, etc.)   │  │   (functions, refs)  │             │
│  └──────────────────────┘  └──────────────────────┘             │
│                                                                  │
│  ┌──────────────────────┐  ┌──────────────────────┐             │
│  │   Module Scope       │  │   Global Variables   │             │
│  │   (imports, consts)  │  │   (window.*)         │             │
│  └──────────────────────┘  └──────────────────────┘             │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        Browser Storage                           │
│                                                                  │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌───────────┐ │
│  │localStorage│  │sessionStor.│  │  Cookies   │  │ IndexedDB │ │
│  │  (~5MB)    │  │   (~5MB)   │  │   (~4KB)   │  │  (large)  │ │
│  └────────────┘  └────────────┘  └────────────┘  └───────────┘ │
└─────────────────────────────────────────────────────────────────┘

JavaScript Heap: Cleared on page reload
Browser Storage: Persists across reloads (until cleared)
SPA中的状态生命周期:
javascript
// 1. Page loads, JavaScript executes
// Heap is empty

// 2. Components mount, state initialized
const [count, setCount] = useState(0);  // Allocated in heap

// 3. User interacts, state changes
setCount(1);  // New value in heap, old garbage collected

// 4. Navigation within SPA
// Heap PERSISTS - state survives navigation!

// 5. Page reload or close
// Heap DESTROYED - all in-memory state lost
// Only browser storage survives

State Updates: Synchronous vs Asynchronous

状态更新:同步 vs 异步

React batches state updates for performance:
javascript
// You might expect this to increment by 3:
function handleClick() {
  setCount(count + 1);  // count is 0, sets to 1
  setCount(count + 1);  // count is STILL 0, sets to 1
  setCount(count + 1);  // count is STILL 0, sets to 1
}
// Result: count is 1, not 3!

// WHY?
// React batches updates and uses the SAME count value for all three
// count doesn't change until the next render

// SOLUTION: Functional updates
function handleClick() {
  setCount(c => c + 1);  // c is 0, returns 1
  setCount(c => c + 1);  // c is 1, returns 2
  setCount(c => c + 1);  // c is 2, returns 3
}
// Result: count is 3!

// Each function receives the LATEST pending state value
React会批量处理状态更新以提升性能:
javascript
// You might expect this to increment by 3:
function handleClick() {
  setCount(count + 1);  // count is 0, sets to 1
  setCount(count + 1);  // count is STILL 0, sets to 1
  setCount(count + 1);  // count is STILL 0, sets to 1
}
// Result: count is 1, not 3!

// WHY?
// React batches updates and uses the SAME count value for all three
// count doesn't change until the next render

// SOLUTION: Functional updates
function handleClick() {
  setCount(c => c + 1);  // c is 0, returns 1
  setCount(c => c + 1);  // c is 1, returns 2
  setCount(c => c + 1);  // c is 2, returns 3
}
// Result: count is 3!

// Each function receives the LATEST pending state value

Immutability: Why It Matters

不可变性:为什么重要

React uses reference equality to detect changes:
javascript
// MUTABLE UPDATE (WRONG)
const [user, setUser] = useState({ name: 'John', age: 30 });

function birthday() {
  user.age = 31;  // Mutating existing object
  setUser(user);  // Same reference!
}
// React sees: old reference === new reference
// React thinks: "nothing changed, skip re-render"
// Bug: UI doesn't update!


// IMMUTABLE UPDATE (CORRECT)
function birthday() {
  setUser({ ...user, age: 31 });  // New object!
}
// React sees: old reference !== new reference
// React knows: "state changed, re-render"


// NESTED IMMUTABILITY
const [state, setState] = useState({
  user: {
    profile: {
      name: 'John',
      settings: { theme: 'dark' }
    }
  }
});

// Updating deeply nested property:
setState({
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      settings: {
        ...state.user.profile.settings,
        theme: 'light'  // The actual change
      }
    }
  }
});

// This is verbose! Libraries like Immer help:
setState(produce(state, draft => {
  draft.user.profile.settings.theme = 'light';
}));
React通过引用相等性检测变化:
javascript
// MUTABLE UPDATE (WRONG)
const [user, setUser] = useState({ name: 'John', age: 30 });

function birthday() {
  user.age = 31;  // Mutating existing object
  setUser(user);  // Same reference!
}
// React sees: old reference === new reference
// React thinks: "nothing changed, skip re-render"
// Bug: UI doesn't update!


// IMMUTABLE UPDATE (CORRECT)
function birthday() {
  setUser({ ...user, age: 31 });  // New object!
}
// React sees: old reference !== new reference
// React knows: "state changed, re-render"


// NESTED IMMUTABILITY
const [state, setState] = useState({
  user: {
    profile: {
      name: 'John',
      settings: { theme: 'dark' }
    }
  }
});

// Updating deeply nested property:
setState({
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      settings: {
        ...state.user.profile.settings,
        theme: 'light'  // The actual change
      }
    }
  }
});

// This is verbose! Libraries like Immer help:
setState(produce(state, draft => {
  draft.user.profile.settings.theme = 'light';
}));

Context: How It Actually Works

Context:实际工作原理

React Context creates a "wormhole" through the component tree:
javascript
// WITHOUT CONTEXT: Prop drilling
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />   // Finally used here!
    </Sidebar>
  </Layout>
</App>
// Every intermediate component must forward the prop


// WITH CONTEXT: Direct access
const UserContext = createContext(null);

<UserContext.Provider value={user}>
  <App>
    <Layout>
      <Sidebar>
        <UserMenu />  // Can access user directly!
      </Sidebar>
    </Layout>
  </App>
</UserContext.Provider>

function UserMenu() {
  const user = useContext(UserContext);  // "Wormhole" access
  return <span>{user.name}</span>;
}
Context performance gotcha:
javascript
// PROBLEM: Context changes re-render ALL consumers

const AppContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  // Every state in one context
  return (
    <AppContext.Provider value={{ user, theme, notifications }}>
      <Header />      {/* Re-renders when ANYTHING changes */}
      <Main />        {/* Re-renders when ANYTHING changes */}
      <Footer />      {/* Re-renders when ANYTHING changes */}
    </AppContext.Provider>
  );
}

// SOLUTION: Split contexts by update frequency

<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    <NotificationContext.Provider value={notifications}>
      <App />
    </NotificationContext.Provider>
  </ThemeContext.Provider>
</UserContext.Provider>

// Now: theme change only re-renders theme consumers
React Context为组件树创建了一个“虫洞”:
javascript
// WITHOUT CONTEXT: Prop drilling
<App user={user}>
  <Layout user={user}>
    <Sidebar user={user}>
      <UserMenu user={user} />   // Finally used here!
    </Sidebar>
  </Layout>
</App>
// Every intermediate component must forward the prop


// WITH CONTEXT: Direct access
const UserContext = createContext(null);

<UserContext.Provider value={user}>
  <App>
    <Layout>
      <Sidebar>
        <UserMenu />  // Can access user directly!
      </Sidebar>
    </Layout>
  </App>
</UserContext.Provider>

function UserMenu() {
  const user = useContext(UserContext);  // "Wormhole" access
  return <span>{user.name}</span>;
}
Context性能陷阱:
javascript
// PROBLEM: Context changes re-render ALL consumers

const AppContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  // Every state in one context
  return (
    <AppContext.Provider value={{ user, theme, notifications }}>
      <Header />      {/* Re-renders when ANYTHING changes */}
      <Main />        {/* Re-renders when ANYTHING changes */}
      <Footer />      {/* Re-renders when ANYTHING changes */}
    </AppContext.Provider>
  );
}

// SOLUTION: Split contexts by update frequency

<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    <NotificationContext.Provider value={notifications}>
      <App />
    </NotificationContext.Provider>
  </ThemeContext.Provider>
</UserContext.Provider>

// Now: theme change only re-renders theme consumers

Server State: A Different Beast

服务端状态:特殊的存在

Server state has fundamentally different characteristics:
javascript
// CLIENT STATE:
// - Created locally
// - You control it completely
// - Synchronous access
// - Always fresh (it's the source of truth)

const [isOpen, setIsOpen] = useState(false);
// isOpen is ALWAYS correct - you just set it


// SERVER STATE:
// - Created remotely
// - You have a COPY, not the source
// - Asynchronous access
// - Potentially stale (someone else may have changed it)

const [products, setProducts] = useState(null);

useEffect(() => {
  fetch('/api/products').then(r => r.json()).then(setProducts);
}, []);

// products might be:
// - null (loading)
// - stale (database changed since fetch)
// - from a failed request (error)
// - cached (do we refetch?)
Why server state libraries exist:
javascript
// Manual server state management:
function ProductList() {
  const [products, setProducts] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const [lastFetched, setLastFetched] = useState(null);
  
  const fetchProducts = async () => {
    setIsLoading(true);
    setError(null);
    try {
      const res = await fetch('/api/products');
      if (!res.ok) throw new Error('Failed');
      const data = await res.json();
      setProducts(data);
      setLastFetched(Date.now());
    } catch (e) {
      setError(e);
    } finally {
      setIsLoading(false);
    }
  };
  
  useEffect(() => {
    fetchProducts();
    // Refetch on window focus?
    // Refetch on interval?
    // Cache between components?
    // Cancel on unmount?
  }, []);
  
  // This is getting complex...
}

// With TanStack Query:
function ProductList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
    staleTime: 60000,           // Fresh for 1 minute
    refetchOnWindowFocus: true,  // Refetch when user returns
    retry: 3,                    // Retry failed requests
  });
  
  // All the complexity is handled
}
服务端状态具有本质不同的特性:
javascript
// CLIENT STATE:
// - Created locally
// - You control it completely
// - Synchronous access
// - Always fresh (it's the source of truth)

const [isOpen, setIsOpen] = useState(false);
// isOpen is ALWAYS correct - you just set it


// SERVER STATE:
// - Created remotely
// - You have a COPY, not the source
// - Asynchronous access
// - Potentially stale (someone else may have changed it)

const [products, setProducts] = useState(null);

useEffect(() => {
  fetch('/api/products').then(r => r.json()).then(setProducts);
}, []);

// products might be:
// - null (loading)
// - stale (database changed since fetch)
// - from a failed request (error)
// - cached (do we refetch?)
为什么需要服务端状态管理库:
javascript
// Manual server state management:
function ProductList() {
  const [products, setProducts] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const [lastFetched, setLastFetched] = useState(null);
  
  const fetchProducts = async () => {
    setIsLoading(true);
    setError(null);
    try {
      const res = await fetch('/api/products');
      if (!res.ok) throw new Error('Failed');
      const data = await res.json();
      setProducts(data);
      setLastFetched(Date.now());
    } catch (e) {
      setError(e);
    } finally {
      setIsLoading(false);
    }
  };
  
  useEffect(() => {
    fetchProducts();
    // Refetch on window focus?
    // Refetch on interval?
    // Cache between components?
    // Cancel on unmount?
  }, []);
  
  // This is getting complex...
}

// With TanStack Query:
function ProductList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
    staleTime: 60000,           // Fresh for 1 minute
    refetchOnWindowFocus: true,  // Refetch when user returns
    retry: 3,                    // Retry failed requests
  });
  
  // All the complexity is handled
}

URL State: The Serialization Challenge

URL状态:序列化挑战

URL state must be serializable to a string:
javascript
// URL: /products?category=shoes&sizes=7,8,9&page=2&sort=price-asc

// STATE OBJECT:
{
  category: 'shoes',
  sizes: [7, 8, 9],
  page: 2,
  sort: { field: 'price', order: 'asc' }
}

// SERIALIZATION CHALLENGES:

// Arrays: Multiple values for same key or comma-separated?
// ?sizes=7&sizes=8&sizes=9  vs  ?sizes=7,8,9

// Objects: How to represent nested data?
// ?sort=price-asc  (custom encoding)
// ?sortField=price&sortOrder=asc  (flattened)

// Types: Everything is a string in URLs
// ?page=2  → page is "2" (string), need to parse to number

// MANAGING URL STATE:
import { useSearchParams } from 'next/navigation';

function ProductsPage() {
  const searchParams = useSearchParams();
  
  // Reading
  const category = searchParams.get('category');
  const page = parseInt(searchParams.get('page') || '1');
  const sizes = searchParams.get('sizes')?.split(',').map(Number) || [];
  
  // Writing
  function setFilters(newFilters) {
    const params = new URLSearchParams();
    if (newFilters.category) params.set('category', newFilters.category);
    if (newFilters.page > 1) params.set('page', String(newFilters.page));
    if (newFilters.sizes.length) params.set('sizes', newFilters.sizes.join(','));
    
    router.push(`/products?${params.toString()}`);
  }
}
URL状态必须可序列化为字符串:
javascript
// URL: /products?category=shoes&sizes=7,8,9&page=2&sort=price-asc

// STATE OBJECT:
{
  category: 'shoes',
  sizes: [7, 8, 9],
  page: 2,
  sort: { field: 'price', order: 'asc' }
}

// SERIALIZATION CHALLENGES:

// Arrays: Multiple values for same key or comma-separated?
// ?sizes=7&sizes=8&sizes=9  vs  ?sizes=7,8,9

// Objects: How to represent nested data?
// ?sort=price-asc  (custom encoding)
// ?sortField=price&sortOrder=asc  (flattened)

// Types: Everything is a string in URLs
// ?page=2  → page is "2" (string), need to parse to number

// MANAGING URL STATE:
import { useSearchParams } from 'next/navigation';

function ProductsPage() {
  const searchParams = useSearchParams();
  
  // Reading
  const category = searchParams.get('category');
  const page = parseInt(searchParams.get('page') || '1');
  const sizes = searchParams.get('sizes')?.split(',').map(Number) || [];
  
  // Writing
  function setFilters(newFilters) {
    const params = new URLSearchParams();
    if (newFilters.category) params.set('category', newFilters.category);
    if (newFilters.page > 1) params.set('page', String(newFilters.page));
    if (newFilters.sizes.length) params.set('sizes', newFilters.sizes.join(','));
    
    router.push(`/products?${params.toString()}`);
  }
}

State Machines: Eliminating Impossible States

状态机:消除不可能的状态

Complex state often has implicit rules:
javascript
// BOOLEAN SOUP:
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [data, setData] = useState(null);

// BUGS WAITING TO HAPPEN:
// - What if isLoading AND isError are both true?
// - What if isSuccess is true but data is null?
// - Caller must set multiple booleans correctly

async function fetchData() {
  setIsLoading(true);
  setIsError(false);    // Don't forget!
  setIsSuccess(false);  // Don't forget!
  
  try {
    const result = await fetch('/api/data');
    setData(result);
    setIsSuccess(true);
  } catch (e) {
    setIsError(true);
  } finally {
    setIsLoading(false);
  }
}


// STATE MACHINE APPROACH:
type State = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success', data: Data }
  | { status: 'error', error: Error };

// ONLY ONE STATE POSSIBLE AT A TIME
const [state, setState] = useState<State>({ status: 'idle' });

async function fetchData() {
  setState({ status: 'loading' });  // One call, complete transition
  
  try {
    const data = await fetch('/api/data');
    setState({ status: 'success', data });  // One call, complete transition
  } catch (error) {
    setState({ status: 'error', error });   // One call, complete transition
  }
}

// USAGE:
switch (state.status) {
  case 'idle': return <button onClick={fetchData}>Load</button>;
  case 'loading': return <Spinner />;
  case 'success': return <DataView data={state.data} />;
  case 'error': return <Error message={state.error.message} />;
}
// TypeScript knows exactly what's available in each case
复杂状态转换场景:
javascript
// BOOLEAN SOUP:
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
// BUGS WAITING TO HAPPEN:
// - What if isLoading AND isError are both true?
// - What if isSuccess is true but data is null?
// - Caller must set multiple booleans correctly

async function fetchData() {
  setIsLoading(true);
  setIsError(false);    // Don't forget!
  setIsSuccess(false);  // Don't forget!
  
  try {
    const result = await fetch('/api/data');
    setData(result);
    setIsSuccess(true);
  } catch (e) {
    setIsError(true);
  } finally {
    setIsLoading(false);
  }
}


// STATE MACHINE APPROACH:
type State = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success', data: Data }
  | { status: 'error', error: Error };

// ONLY ONE STATE POSSIBLE AT A TIME
const [state, setState] = useState<State>({ status: 'idle' });

async function fetchData() {
  setState({ status: 'loading' });  // One call, complete transition
  
  try {
    const data = await fetch('/api/data');
    setState({ status: 'success', data });  // One call, complete transition
  } catch (error) {
    setState({ status: 'error', error });   // One call, complete transition
  }
}

// USAGE:
switch (state.status) {
  case 'idle': return <button onClick={fetchData}>Load</button>;
  case 'loading': return <Spinner />;
  case 'success': return <DataView data={state.data} />;
  case 'error': return <Error message={state.error.message} />;
}
// TypeScript knows exactly what's available in each case

The Single Source of Truth Principle

单一数据源原则

Every piece of state should have ONE authoritative location:
javascript
// VIOLATION: Same data in multiple places

// User data in context
const userContext = { user: { name: 'John' } };

// User data also in component state
const [userData, setUserData] = useState({ name: 'John' });

// User data also in form state
const [formName, setFormName] = useState('John');

// NOW: Which is correct if they differ?
// - User edits form: formName = 'Jane'
// - userData still says 'John'
// - userContext still says 'John'
// CONFLICT!


// CORRECT: Single source of truth

// User data lives in ONE place (context)
const { user, updateUser } = useUserContext();

// Form uses a DRAFT (clearly temporary)
const [draft, setDraft] = useState(user.name);

// On save, update the source of truth
function handleSave() {
  updateUser({ ...user, name: draft });
  // Now everything derives from the same source
}

// Components READ from the source
function Header() {
  const { user } = useUserContext();
  return <span>{user.name}</span>;  // Always reflects truth
}

每个状态都应该有唯一的权威存储位置:
javascript
// VIOLATION: Same data in multiple places

// User data in context
const userContext = { user: { name: 'John' } };

// User data also in component state
const [userData, setUserData] = useState({ name: 'John' });

// User data also in form state
const [formName, setFormName] = useState('John');

// NOW: Which is correct if they differ?
// - User edits form: formName = 'Jane'
// - userData still says 'John'
// - userContext still says 'John'
// CONFLICT!


// CORRECT: Single source of truth

// User data lives in ONE place (context)
const { user, updateUser } = useUserContext();

// Form uses a DRAFT (clearly temporary)
const [draft, setDraft] = useState(user.name);

// On save, update the source of truth
function handleSave() {
  updateUser({ ...user, name: draft });
  // Now everything derives from the same source
}

// Components READ from the source
function Header() {
  const { user } = useUserContext();
  return <span>{user.name}</span>;  // Always reflects truth
}

For Framework Authors: Building State Management Systems

框架开发者指南:构建状态管理系统

Implementation Note: The patterns and code examples below represent one proven approach to building state management systems. Reactivity can be implemented many ways—Vue uses proxies, Solid uses signals, Svelte uses compile-time reactivity, and React uses explicit setState. The direction shown here (signals-based) is increasingly popular but not the only valid approach. Choose based on your framework's rendering model, bundle size goals, and developer ergonomics.
实现说明:以下模式和代码示例代表了构建状态管理系统的一种成熟方法。响应式系统有多种实现方式——Vue使用代理,Solid使用信号,Svelte使用编译时响应式,React使用显式setState。此处展示的基于信号的实现方式日益流行,但并非唯一有效方案。请根据框架的渲染模型、包大小目标和开发者体验选择合适的方案。

Implementing a Reactivity System

实现响应式系统

javascript
// SIGNALS-BASED REACTIVITY (SolidJS/Preact-style)

let currentObserver = null;
let batchedUpdates = [];
let isBatching = false;

function createSignal(initialValue) {
  let value = initialValue;
  const subscribers = new Set();
  
  function read() {
    // Track dependency
    if (currentObserver) {
      subscribers.add(currentObserver);
    }
    return value;
  }
  
  function write(newValue) {
    if (value !== newValue) {
      value = newValue;
      
      if (isBatching) {
        // Queue updates
        subscribers.forEach(s => {
          if (!batchedUpdates.includes(s)) {
            batchedUpdates.push(s);
          }
        });
      } else {
        // Execute immediately
        subscribers.forEach(s => s());
      }
    }
  }
  
  return [read, write];
}

function createEffect(fn) {
  const effect = () => {
    // Clear previous dependencies
    currentObserver = effect;
    try {
      fn();
    } finally {
      currentObserver = null;
    }
  };
  
  // Run immediately to collect dependencies
  effect();
}

function createMemo(fn) {
  const [value, setValue] = createSignal(undefined);
  createEffect(() => setValue(fn()));
  return value;
}

function batch(fn) {
  isBatching = true;
  try {
    fn();
  } finally {
    isBatching = false;
    // Flush batched updates
    const updates = [...new Set(batchedUpdates)];
    batchedUpdates = [];
    updates.forEach(u => u());
  }
}

// Usage
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal('World');

const greeting = createMemo(() => `Hello, ${name()}! Count: ${count()}`);

createEffect(() => {
  console.log(greeting()); // Re-runs when count or name change
});

batch(() => {
  setCount(1);
  setName('User');
  // Only one console.log, not two
});
javascript
// SIGNALS-BASED REACTIVITY (SolidJS/Preact-style)

let currentObserver = null;
let batchedUpdates = [];
let isBatching = false;

function createSignal(initialValue) {
  let value = initialValue;
  const subscribers = new Set();
  
  function read() {
    // Track dependency
    if (currentObserver) {
      subscribers.add(currentObserver);
    }
    return value;
  }
  
  function write(newValue) {
    if (value !== newValue) {
      value = newValue;
      
      if (isBatching) {
        // Queue updates
        subscribers.forEach(s => {
          if (!batchedUpdates.includes(s)) {
            batchedUpdates.push(s);
          }
        });
      } else {
        // Execute immediately
        subscribers.forEach(s => s());
      }
    }
  }
  
  return [read, write];
}

function createEffect(fn) {
  const effect = () => {
    // Clear previous dependencies
    currentObserver = effect;
    try {
      fn();
    } finally {
      currentObserver = null;
    }
  };
  
  // Run immediately to collect dependencies
  effect();
}

function createMemo(fn) {
  const [value, setValue] = createSignal(undefined);
  createEffect(() => setValue(fn()));
  return value;
}

function batch(fn) {
  isBatching = true;
  try {
    fn();
  } finally {
    isBatching = false;
    // Flush batched updates
    const updates = [...new Set(batchedUpdates)];
    batchedUpdates = [];
    updates.forEach(u => u());
  }
}

// Usage
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal('World');

const greeting = createMemo(() => `Hello, ${name()}! Count: ${count()}`);

createEffect(() => {
  console.log(greeting()); // Re-runs when count or name change
});

batch(() => {
  setCount(1);
  setName('User');
  // Only one console.log, not two
});

Building a Store System

构建全局存储系统

javascript
// GLOBAL STORE IMPLEMENTATION

function createStore(initialState) {
  let state = initialState;
  const listeners = new Set();
  
  function getState() {
    return state;
  }
  
  function setState(updater) {
    const nextState = typeof updater === 'function' 
      ? updater(state) 
      : updater;
    
    // Shallow merge for objects
    if (typeof nextState === 'object' && !Array.isArray(nextState)) {
      state = { ...state, ...nextState };
    } else {
      state = nextState;
    }
    
    // Notify listeners
    listeners.forEach(listener => listener(state));
  }
  
  function subscribe(listener) {
    listeners.add(listener);
    return () => listeners.delete(listener);
  }
  
  // Selector-based subscription (only notify on selected change)
  function subscribeWithSelector(selector, listener) {
    let currentSelection = selector(state);
    
    return subscribe((newState) => {
      const newSelection = selector(newState);
      if (!Object.is(currentSelection, newSelection)) {
        currentSelection = newSelection;
        listener(newSelection);
      }
    });
  }
  
  return { getState, setState, subscribe, subscribeWithSelector };
}

// React integration
function useStore(store, selector = s => s) {
  const [state, setState] = useState(() => selector(store.getState()));
  
  useEffect(() => {
    return store.subscribeWithSelector(selector, setState);
  }, [store, selector]);
  
  return state;
}

// Middleware support
function applyMiddleware(...middlewares) {
  return (createStore) => (initialState) => {
    const store = createStore(initialState);
    
    let dispatch = store.setState;
    
    const api = {
      getState: store.getState,
      dispatch: (action) => dispatch(action),
    };
    
    const chain = middlewares.map(mw => mw(api));
    dispatch = compose(...chain)(store.setState);
    
    return { ...store, setState: dispatch };
  };
}

// Logger middleware
const logger = (api) => (next) => (action) => {
  console.log('Before:', api.getState());
  next(action);
  console.log('After:', api.getState());
};
javascript
// GLOBAL STORE IMPLEMENTATION

function createStore(initialState) {
  let state = initialState;
  const listeners = new Set();
  
  function getState() {
    return state;
  }
  
  function setState(updater) {
    const nextState = typeof updater === 'function' 
      ? updater(state) 
      : updater;
    
    // Shallow merge for objects
    if (typeof nextState === 'object' && !Array.isArray(nextState)) {
      state = { ...state, ...nextState };
    } else {
      state = nextState;
    }
    
    // Notify listeners
    listeners.forEach(listener => listener(state));
  }
  
  function subscribe(listener) {
    listeners.add(listener);
    return () => listeners.delete(listener);
  }
  
  // Selector-based subscription (only notify on selected change)
  function subscribeWithSelector(selector, listener) {
    let currentSelection = selector(state);
    
    return subscribe((newState) => {
      const newSelection = selector(newState);
      if (!Object.is(currentSelection, newSelection)) {
        currentSelection = newSelection;
        listener(newSelection);
      }
    });
  }
  
  return { getState, setState, subscribe, subscribeWithSelector };
}

// React integration
function useStore(store, selector = s => s) {
  const [state, setState] = useState(() => selector(store.getState()));
  
  useEffect(() => {
    return store.subscribeWithSelector(selector, setState);
  }, [store, selector]);
  
  return state;
}

// Middleware support
function applyMiddleware(...middlewares) {
  return (createStore) => (initialState) => {
    const store = createStore(initialState);
    
    let dispatch = store.setState;
    
    const api = {
      getState: store.getState,
      dispatch: (action) => dispatch(action),
    };
    
    const chain = middlewares.map(mw => mw(api));
    dispatch = compose(...chain)(store.setState);
    
    return { ...store, setState: dispatch };
  };
}

// Logger middleware
const logger = (api) => (next) => (action) => {
  console.log('Before:', api.getState());
  next(action);
  console.log('After:', api.getState());
};

Implementing Context System

实现Context系统

javascript
// CONTEXT IMPLEMENTATION

const contextMap = new Map();

function createContext(defaultValue) {
  const id = Symbol('context');
  
  function Provider({ value, children }) {
    // Store value in render context
    const prevValue = contextMap.get(id);
    contextMap.set(id, value);
    
    try {
      return children;
    } finally {
      // Restore on cleanup
      if (prevValue !== undefined) {
        contextMap.set(id, prevValue);
      } else {
        contextMap.delete(id);
      }
    }
  }
  
  function useContext() {
    return contextMap.has(id) ? contextMap.get(id) : defaultValue;
  }
  
  return { Provider, useContext, id };
}

// Scoped context (for server components)
class ContextScope {
  constructor(parent = null) {
    this.values = new Map();
    this.parent = parent;
  }
  
  provide(context, value) {
    this.values.set(context.id, value);
  }
  
  consume(context) {
    if (this.values.has(context.id)) {
      return this.values.get(context.id);
    }
    if (this.parent) {
      return this.parent.consume(context);
    }
    return context.defaultValue;
  }
  
  fork() {
    return new ContextScope(this);
  }
}

// Async context (for server-side request scope)
import { AsyncLocalStorage } from 'async_hooks';

const asyncContext = new AsyncLocalStorage();

function runWithContext(context, fn) {
  return asyncContext.run(context, fn);
}

function getCurrentContext() {
  return asyncContext.getStore();
}
javascript
// CONTEXT IMPLEMENTATION

const contextMap = new Map();

function createContext(defaultValue) {
  const id = Symbol('context');
  
  function Provider({ value, children }) {
    // Store value in render context
    const prevValue = contextMap.get(id);
    contextMap.set(id, value);
    
    try {
      return children;
    } finally {
      // Restore on cleanup
      if (prevValue !== undefined) {
        contextMap.set(id, prevValue);
      } else {
        contextMap.delete(id);
      }
    }
  }
  
  function useContext() {
    return contextMap.has(id) ? contextMap.get(id) : defaultValue;
  }
  
  return { Provider, useContext, id };
}

// Scoped context (for server components)
class ContextScope {
  constructor(parent = null) {
    this.values = new Map();
    this.parent = parent;
  }
  
  provide(context, value) {
    this.values.set(context.id, value);
  }
  
  consume(context) {
    if (this.values.has(context.id)) {
      return this.values.get(context.id);
    }
    if (this.parent) {
      return this.parent.consume(context);
    }
    return context.defaultValue;
  }
  
  fork() {
    return new ContextScope(this);
  }
}

// Async context (for server-side request scope)
import { AsyncLocalStorage } from 'async_hooks';

const asyncContext = new AsyncLocalStorage();

function runWithContext(context, fn) {
  return asyncContext.run(context, fn);
}

function getCurrentContext() {
  return asyncContext.getStore();
}

Server State Cache Implementation

服务端状态缓存实现

javascript
// SERVER STATE CACHE (TanStack Query style)

class QueryClient {
  constructor() {
    this.cache = new Map();
    this.subscribers = new Map();
    this.defaultOptions = {
      staleTime: 0,
      cacheTime: 5 * 60 * 1000,
      retry: 3,
    };
  }
  
  getQueryData(key) {
    const keyStr = JSON.stringify(key);
    return this.cache.get(keyStr)?.data;
  }
  
  setQueryData(key, updater) {
    const keyStr = JSON.stringify(key);
    const current = this.cache.get(keyStr);
    const newData = typeof updater === 'function' 
      ? updater(current?.data) 
      : updater;
    
    this.cache.set(keyStr, {
      data: newData,
      timestamp: Date.now(),
      status: 'success',
    });
    
    this.notifySubscribers(keyStr);
  }
  
  invalidateQueries(key) {
    const keyStr = JSON.stringify(key);
    const entry = this.cache.get(keyStr);
    if (entry) {
      entry.isStale = true;
      this.notifySubscribers(keyStr);
    }
  }
  
  subscribe(key, callback) {
    const keyStr = JSON.stringify(key);
    if (!this.subscribers.has(keyStr)) {
      this.subscribers.set(keyStr, new Set());
    }
    this.subscribers.get(keyStr).add(callback);
    
    return () => {
      this.subscribers.get(keyStr)?.delete(callback);
    };
  }
  
  notifySubscribers(keyStr) {
    this.subscribers.get(keyStr)?.forEach(cb => cb());
  }
}

// Query hook implementation
function useQuery(key, queryFn, options = {}) {
  const client = useQueryClient();
  const [state, setState] = useState(() => {
    const cached = client.getQueryData(key);
    return {
      data: cached,
      isLoading: !cached,
      error: null,
      isStale: true,
    };
  });
  
  useEffect(() => {
    const keyStr = JSON.stringify(key);
    let cancelled = false;
    
    async function fetch() {
      setState(s => ({ ...s, isLoading: true }));
      
      try {
        const data = await queryFn();
        if (!cancelled) {
          client.setQueryData(key, data);
          setState({ data, isLoading: false, error: null, isStale: false });
        }
      } catch (error) {
        if (!cancelled) {
          setState(s => ({ ...s, isLoading: false, error }));
        }
      }
    }
    
    // Check if data is stale
    const cached = client.cache.get(keyStr);
    const isStale = !cached || 
      Date.now() - cached.timestamp > (options.staleTime || 0);
    
    if (isStale) {
      fetch();
    }
    
    // Subscribe to invalidations
    const unsubscribe = client.subscribe(key, fetch);
    
    return () => {
      cancelled = true;
      unsubscribe();
    };
  }, [JSON.stringify(key)]);
  
  return state;
}
javascript
// SERVER STATE CACHE (TanStack Query style)

class QueryClient {
  constructor() {
    this.cache = new Map();
    this.subscribers = new Map();
    this.defaultOptions = {
      staleTime: 0,
      cacheTime: 5 * 60 * 1000,
      retry: 3,
    };
  }
  
  getQueryData(key) {
    const keyStr = JSON.stringify(key);
    return this.cache.get(keyStr)?.data;
  }
  
  setQueryData(key, updater) {
    const keyStr = JSON.stringify(key);
    const current = this.cache.get(keyStr);
    const newData = typeof updater === 'function' 
      ? updater(current?.data) 
      : updater;
    
    this.cache.set(keyStr, {
      data: newData,
      timestamp: Date.now(),
      status: 'success',
    });
    
    this.notifySubscribers(keyStr);
  }
  
  invalidateQueries(key) {
    const keyStr = JSON.stringify(key);
    const entry = this.cache.get(keyStr);
    if (entry) {
      entry.isStale = true;
      this.notifySubscribers(keyStr);
    }
  }
  
  subscribe(key, callback) {
    const keyStr = JSON.stringify(key);
    if (!this.subscribers.has(keyStr)) {
      this.subscribers.set(keyStr, new Set());
    }
    this.subscribers.get(keyStr).add(callback);
    
    return () => {
      this.subscribers.get(keyStr)?.delete(callback);
    };
  }
  
  notifySubscribers(keyStr) {
    this.subscribers.get(keyStr)?.forEach(cb => cb());
  }
}

// Query hook implementation
function useQuery(key, queryFn, options = {}) {
  const client = useQueryClient();
  const [state, setState] = useState(() => {
    const cached = client.getQueryData(key);
    return {
      data: cached,
      isLoading: !cached,
      error: null,
      isStale: true,
    };
  });
  
  useEffect(() => {
    const keyStr = JSON.stringify(key);
    let cancelled = false;
    
    async function fetch() {
      setState(s => ({ ...s, isLoading: true }));
      
      try {
        const data = await queryFn();
        if (!cancelled) {
          client.setQueryData(key, data);
          setState({ data, isLoading: false, error: null, isStale: false });
        }
      } catch (error) {
        if (!cancelled) {
          setState(s => ({ ...s, isLoading: false, error }));
        }
      }
    }
    
    // Check if data is stale
    const cached = client.cache.get(keyStr);
    const isStale = !cached || 
      Date.now() - cached.timestamp > (options.staleTime || 0);
    
    if (isStale) {
      fetch();
    }
    
    // Subscribe to invalidations
    const unsubscribe = client.subscribe(key, fetch);
    
    return () => {
      cancelled = true;
      unsubscribe();
    };
  }, [JSON.stringify(key)]);
  
  return state;
}

URL State Synchronization

URL状态同步实现

javascript
// URL STATE SYNC IMPLEMENTATION

function createURLState(schema) {
  function parse() {
    const params = new URLSearchParams(window.location.search);
    const state = {};
    
    for (const [key, config] of Object.entries(schema)) {
      const value = params.get(key);
      
      if (value === null) {
        state[key] = config.default;
      } else {
        // Parse based on type
        switch (config.type) {
          case 'number':
            state[key] = Number(value);
            break;
          case 'boolean':
            state[key] = value === 'true';
            break;
          case 'array':
            state[key] = value.split(',');
            break;
          default:
            state[key] = value;
        }
      }
    }
    
    return state;
  }
  
  function stringify(state) {
    const params = new URLSearchParams();
    
    for (const [key, value] of Object.entries(state)) {
      const config = schema[key];
      
      // Skip defaults
      if (value === config?.default) continue;
      
      if (Array.isArray(value)) {
        params.set(key, value.join(','));
      } else if (value !== undefined && value !== null) {
        params.set(key, String(value));
      }
    }
    
    return params.toString();
  }
  
  function update(newState, options = { replace: false }) {
    const merged = { ...parse(), ...newState };
    const queryString = stringify(merged);
    const url = queryString 
      ? `${window.location.pathname}?${queryString}`
      : window.location.pathname;
    
    if (options.replace) {
      history.replaceState(null, '', url);
    } else {
      history.pushState(null, '', url);
    }
    
    // Dispatch custom event for listeners
    window.dispatchEvent(new CustomEvent('urlstatechange', { detail: merged }));
  }
  
  return { parse, stringify, update };
}

// React hook
function useURLState(schema) {
  const urlState = useMemo(() => createURLState(schema), []);
  const [state, setState] = useState(urlState.parse);
  
  useEffect(() => {
    const handler = () => setState(urlState.parse());
    window.addEventListener('popstate', handler);
    window.addEventListener('urlstatechange', handler);
    return () => {
      window.removeEventListener('popstate', handler);
      window.removeEventListener('urlstatechange', handler);
    };
  }, []);
  
  const setURLState = useCallback((updates) => {
    urlState.update(updates);
  }, []);
  
  return [state, setURLState];
}
javascript
// URL STATE SYNC IMPLEMENTATION

function createURLState(schema) {
  function parse() {
    const params = new URLSearchParams(window.location.search);
    const state = {};
    
    for (const [key, config] of Object.entries(schema)) {
      const value = params.get(key);
      
      if (value === null) {
        state[key] = config.default;
      } else {
        // Parse based on type
        switch (config.type) {
          case 'number':
            state[key] = Number(value);
            break;
          case 'boolean':
            state[key] = value === 'true';
            break;
          case 'array':
            state[key] = value.split(',');
            break;
          default:
            state[key] = value;
        }
      }
    }
    
    return state;
  }
  
  function stringify(state) {
    const params = new URLSearchParams();
    
    for (const [key, value] of Object.entries(state)) {
      const config = schema[key];
      
      // Skip defaults
      if (value === config?.default) continue;
      
      if (Array.isArray(value)) {
        params.set(key, value.join(','));
      } else if (value !== undefined && value !== null) {
        params.set(key, String(value));
      }
    }
    
    return params.toString();
  }
  
  function update(newState, options = { replace: false }) {
    const merged = { ...parse(), ...newState };
    const queryString = stringify(merged);
    const url = queryString 
      ? `${window.location.pathname}?${queryString}`
      : window.location.pathname;
    
    if (options.replace) {
      history.replaceState(null, '', url);
    } else {
      history.pushState(null, '', url);
    }
    
    // Dispatch custom event for listeners
    window.dispatchEvent(new CustomEvent('urlstatechange', { detail: merged }));
  }
  
  return { parse, stringify, update };
}

// React hook
function useURLState(schema) {
  const urlState = useMemo(() => createURLState(schema), []);
  const [state, setState] = useState(urlState.parse);
  
  useEffect(() => {
    const handler = () => setState(urlState.parse());
    window.addEventListener('popstate', handler);
    window.addEventListener('urlstatechange', handler);
    return () => {
      window.removeEventListener('popstate', handler);
      window.removeEventListener('urlstatechange', handler);
    };
  }, []);
  
  const setURLState = useCallback((updates) => {
    urlState.update(updates);
  }, []);
  
  return [state, setURLState];
}

Related Skills

相关技能

  • See web-app-architectures for SPA state persistence
  • See rendering-patterns for server vs client state
  • See routing-patterns for URL state
  • 查看 web-app-architectures 了解SPA状态持久化
  • 查看 rendering-patterns 了解服务端与客户端状态
  • 查看 routing-patterns 了解URL状态