designing-frontend-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDesigning 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
特性
| Feature | Description | Guide |
|---|---|---|
| Compound Components | Shared state via context for flexible component APIs | |
| Custom Hooks | Encapsulate reusable logic (useDebounce, useLocalStorage) | |
| Render Props | Maximum flexibility for data fetching and rendering | |
| State Machines | Predictable state transitions for complex flows | |
| HOCs | Cross-cutting concerns (auth, error boundaries) | |
| Optimistic UI | Instant feedback with rollback on failure | |
| 特性 | 描述 | 指南 |
|---|---|---|
| 复合组件 | 通过上下文共享状态,实现灵活的组件API | |
| 自定义Hooks | 封装可复用逻辑(useDebounce、useLocalStorage) | |
| 渲染属性 | 为数据获取和渲染提供最大灵活性 | |
| 状态机 | 为复杂流程提供可预测的状态转换 | |
| HOCs | 处理横切关注点(权限校验、错误边界) | |
| 乐观UI | 提供即时反馈,失败时支持回滚 | |
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
最佳实践
| Do | Avoid |
|---|---|
| Use compound components for complex UI with shared state | Overusing HOCs (prefer hooks) |
| Create custom hooks to encapsulate reusable logic | Mutating state directly |
| Implement state machines for complex state transitions | Deeply nested component hierarchies |
| Use TypeScript for type-safe component APIs | Passing too many props (use context/composition) |
| Use forwardRef for component library primitives | Creating components with side effects in render |
| Keep components focused with single responsibility | Prop drilling for deeply nested data |
| 推荐做法 | 避免事项 |
|---|---|
| 对带共享状态的复杂UI使用复合组件 | 过度使用HOCs(优先使用Hooks) |
| 创建自定义Hooks封装可复用逻辑 | 直接修改状态 |
| 为复杂状态转换实现状态机 | 过深的组件层级嵌套 |
| 使用TypeScript实现类型安全的组件API | 传递过多props(使用上下文/组合方式) |
| 为组件库基础组件使用forwardRef | 在渲染阶段创建带副作用的组件 |
| 保持组件聚焦,遵循单一职责原则 | 通过Prop Drilling传递深层嵌套数据 |