Files
store_ai_front/.trellis/spec/frontend/state-management.md
2026-05-07 16:14:31 +08:00

4.1 KiB

State Management

This document outlines the strategy and standards for managing state within this project, ensuring data flows predictably and efficiently.


Overview

We follow a tiered state management strategy. Decisions on where to store state are based on the scope of the data and the nesting depth of the components that require it.


State Categories & Selection Criteria

1. Local State (useState)

  • When to use: When the state is only used within a single component or passed down to immediate children via props.
  • Scope: Private to the component or its direct sub-tree.

2. Module/Scoped State (Context API)

  • When to use: When state needs to be accessed by deeply nested components (e.g., Grandparent to Grandchild) within a single functional module, but is not needed globally.
  • Scope: Specific to a feature folder or a logical module.

3. Global State (Zustand)

  • When to use: When data needs to be shared across multiple independent modules or throughout the entire application (e.g., User Auth, Theme, Global Settings).
  • Scope: Application-wide.

Persistence Strategy

To ensure a seamless user experience, we distinguish between data that should survive a page refresh and data that is volatile.

1. Persistent State (LocalStorage)

By default, global state that represents user progress or configuration should be synchronized with LocalStorage.

2. Volatile State (temp.ts)

For data that should not be cached (e.g., temporary UI toggles, scroll positions, transient navigation info), we use a dedicated store.

  • Action: Create a temp.ts file in the store directory.
  • Usage: All non-persistent data must reside here to avoid cluttering the browser's storage.

Zustand Implementation Standards

When creating Zustand stores, follow the naming conventions for state and actions as shown below.

Standard Pattern Example

/**
 * 临时状态存储接口,用于存放不需要持久化的数据
 */
interface TempStore {
    /** 页面滚动条距离顶部的距离 */
    scrollTop: number;
    /** 当前路由的元信息(标题等) */
    routeInfo: routeInfo;
    /** 当前设备是否为移动端 */
    mobile: boolean;

    /** 修改滚动条位置 */
    changeScroll: (value: number) => void;
    /** 更新路由元信息 */
    changeRouterInfo: (value: routeInfo) => void;
    /** 设置移动端状态 */
    setMobile: (value: boolean) => void;
}

const useTempStore = create<TempStore>((set) => ({
    // 初始状态
    scrollTop: 0,
    routeInfo: {
        title: '首页'
    },
    mobile: false,

    // 方法实现
    changeScroll: (value) => {
        set(() => ({ scrollTop: value }));
    },
    changeRouterInfo: (value) => {
        set(() => ({ routeInfo: value }));
    },
    setMobile: (data) => {
        set(() => ({ mobile: data }));
    }
}));

Best Practices

  • Derived State: Whenever possible, calculate values on-the-fly rather than storing them in the state (e.g., don't store isLoggedIn, store the token and derive isLoggedIn = !!token).
  • Action Naming: Use clear verbs for actions:
    • changeX: For updating numerical or incremental values.
    • setX: For overwriting values or booleans.
  • Selectors: Always use selectors when consuming Zustand stores to prevent unnecessary re-renders: const scrollTop = useTempStore((state) => state.scrollTop);
  • Context Cleanup: When using Context, ensure the Provider is wrapped at the lowest common ancestor to minimize the re-render scope.
  • Mandatory Documentation: Every store interface and every single state/action property MUST have a JSDoc /** */ comment written in Chinese.

Common Mistakes

  • Missing Comments: Defining a store or property without a /** */ JSDoc comment.
  • Wrong Language: Writing comments in English. All code comments must be in Chinese.
  • Globalizing everything: Don't put state in Zustand just because it's "easier." If it's local to a form, use useState.
  • Direct State Mutation: Never try to mutate the state directly. Always use the set function provided by the store.