designing-frontend-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Designing Frontend Patterns

前端模式设计

Quick Start

快速开始

tsx
// Compound component pattern with context
const SelectContext = createContext<SelectContextValue | null>(null);

export function Select({ children, value, onValueChange }: SelectProps) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <SelectContext.Provider value={{ isOpen, setIsOpen, value, onValueChange }}>
      <div className="relative">{children}</div>
    </SelectContext.Provider>
  );
}

Select.Trigger = SelectTrigger;
Select.Content = SelectContent;
Select.Item = SelectItem;
tsx
// Compound component pattern with context
const SelectContext = createContext<SelectContextValue | null>(null);

export function Select({ children, value, onValueChange }: SelectProps) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <SelectContext.Provider value={{ isOpen, setIsOpen, value, onValueChange }}>
      <div className="relative">{children}</div>
    </SelectContext.Provider>
  );
}

Select.Trigger = SelectTrigger;
Select.Content = SelectContent;
Select.Item = SelectItem;

Features

特性

FeatureDescriptionGuide
Compound ComponentsShared state via context for flexible component APIs
ref/compound-components.md
Custom HooksEncapsulate reusable logic (useDebounce, useLocalStorage)
ref/custom-hooks.md
Render PropsMaximum flexibility for data fetching and rendering
ref/render-props.md
State MachinesPredictable state transitions for complex flows
ref/state-machines.md
HOCsCross-cutting concerns (auth, error boundaries)
ref/higher-order-components.md
Optimistic UIInstant feedback with rollback on failure
ref/optimistic-updates.md
特性描述指南
复合组件通过上下文共享状态,实现灵活的组件API
ref/compound-components.md
自定义Hooks封装可复用逻辑(useDebounce、useLocalStorage)
ref/custom-hooks.md
渲染属性为数据获取和渲染提供最大灵活性
ref/render-props.md
状态机为复杂流程提供可预测的状态转换
ref/state-machines.md
HOCs处理横切关注点(权限校验、错误边界)
ref/higher-order-components.md
乐观UI提供即时反馈,失败时支持回滚
ref/optimistic-updates.md

Common Patterns

常见模式

Custom Hook with Cleanup

带清理逻辑的自定义Hook

tsx
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [stored, setStored] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch { return initialValue; }
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(stored));
  }, [key, stored]);

  return [stored, setStored] as const;
}
tsx
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [stored, setStored] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch { return initialValue; }
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(stored));
  }, [key, stored]);

  return [stored, setStored] as const;
}

State Machine Pattern

状态机模式

tsx
type FormState = 'idle' | 'validating' | 'submitting' | 'success' | 'error';
type FormEvent =
  | { type: 'SUBMIT'; data: FormData }
  | { type: 'SUCCESS'; response: any }
  | { type: 'ERROR'; error: string };

function useFormMachine() {
  const [state, setState] = useState<FormState>('idle');
  const [context, setContext] = useState({ data: null, error: null });

  const send = useCallback((event: FormEvent) => {
    switch (state) {
      case 'idle':
        if (event.type === 'SUBMIT') { setState('validating'); }
        break;
      case 'submitting':
        if (event.type === 'SUCCESS') { setState('success'); }
        if (event.type === 'ERROR') { setState('error'); setContext(c => ({ ...c, error: event.error })); }
        break;
    }
  }, [state]);

  return { state, context, send };
}
tsx
type FormState = 'idle' | 'validating' | 'submitting' | 'success' | 'error';
type FormEvent =
  | { type: 'SUBMIT'; data: FormData }
  | { type: 'SUCCESS'; response: any }
  | { type: 'ERROR'; error: string };

function useFormMachine() {
  const [state, setState] = useState<FormState>('idle');
  const [context, setContext] = useState({ data: null, error: null });

  const send = useCallback((event: FormEvent) => {
    switch (state) {
      case 'idle':
        if (event.type === 'SUBMIT') { setState('validating'); }
        break;
      case 'submitting':
        if (event.type === 'SUCCESS') { setState('success'); }
        if (event.type === 'ERROR') { setState('error'); setContext(c => ({ ...c, error: event.error })); }
        break;
    }
  }, [state]);

  return { state, context, send };
}

Optimistic Update Hook

乐观更新Hook

tsx
export function useOptimistic<T>(initialData: T, reducer: (state: T, action: any) => T) {
  const [state, setState] = useState({ data: initialData, pending: false, error: null });
  const previousRef = useRef(initialData);

  const optimisticUpdate = useCallback(async (action: any, asyncOp: () => Promise<T>) => {
    previousRef.current = state.data;
    setState({ data: reducer(state.data, action), pending: true, error: null });

    try {
      const result = await asyncOp();
      setState({ data: result, pending: false, error: null });
    } catch (error) {
      setState({ data: previousRef.current, pending: false, error: error as Error });
    }
  }, [state.data, reducer]);

  return { ...state, optimisticUpdate };
}
tsx
export function useOptimistic<T>(initialData: T, reducer: (state: T, action: any) => T) {
  const [state, setState] = useState({ data: initialData, pending: false, error: null });
  const previousRef = useRef(initialData);

  const optimisticUpdate = useCallback(async (action: any, asyncOp: () => Promise<T>) => {
    previousRef.current = state.data;
    setState({ data: reducer(state.data, action), pending: true, error: null });

    try {
      const result = await asyncOp();
      setState({ data: result, pending: false, error: null });
    } catch (error) {
      setState({ data: previousRef.current, pending: false, error: error as Error });
    }
  }, [state.data, reducer]);

  return { ...state, optimisticUpdate };
}

Best Practices

最佳实践

DoAvoid
Use compound components for complex UI with shared stateOverusing HOCs (prefer hooks)
Create custom hooks to encapsulate reusable logicMutating state directly
Implement state machines for complex state transitionsDeeply nested component hierarchies
Use TypeScript for type-safe component APIsPassing too many props (use context/composition)
Use forwardRef for component library primitivesCreating components with side effects in render
Keep components focused with single responsibilityProp drilling for deeply nested data
推荐做法避免事项
对带共享状态的复杂UI使用复合组件过度使用HOCs(优先使用Hooks)
创建自定义Hooks封装可复用逻辑直接修改状态
为复杂状态转换实现状态机过深的组件层级嵌套
使用TypeScript实现类型安全的组件API传递过多props(使用上下文/组合方式)
为组件库基础组件使用forwardRef在渲染阶段创建带副作用的组件
保持组件聚焦,遵循单一职责原则通过Prop Drilling传递深层嵌套数据