前端有哪些设计模式

5 阅读4分钟

前端设计模式详解

前端设计模式是针对Web开发特点优化的一系列解决方案。以下是前端最常用和最重要的设计模式:

📱 组件化模式

1. 容器与展示组件模式

将组件分为两类:容器组件负责业务逻辑和状态管理,展示组件只负责UI渲染。

// 容器组件
class UserListContainer extends React.Component {
  state = { users: [] };
  
  componentDidMount() {
    fetchUsers().then(users => this.setState({ users }));
  }
  
  render() {
    return <UserList users={this.state.users} />;
  }
}

// 展示组件(无状态)
const UserList = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

2. 高阶组件模式

接收一个组件,返回一个增强的新组件。

// HOC示例:添加加载状态
const withLoading = (WrappedComponent) => {
  return class extends React.Component {
    state = { loading: true, data: null };
    
    async componentDidMount() {
      const data = await fetchData();
      this.setState({ loading: false, data });
    }
    
    render() {
      if (this.state.loading) return <Spinner />;
      return <WrappedComponent {...this.props} data={this.state.data} />;
    }
  };
};

// 使用
const EnhancedComponent = withLoading(UserList);

3. Render Props模式

通过函数prop共享组件逻辑。

class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };
  
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  };
  
  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

// 使用
<MouseTracker render={({ x, y }) => (
  <h1>鼠标位置: ({x}, {y})</h1>
)} />

4. 自定义Hook模式

React 16.8+,使用Hook共享状态逻辑。

// 自定义Hook
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return size;
}

// 使用
function ResponsiveComponent() {
  const { width } = useWindowSize();
  return <div>当前宽度: {width}px</div>;
}

🔄 状态管理模式

5. Flux/Redux单向数据流

// Redux架构模式
// 1. Action
const addTodo = (text) => ({
  type: 'ADD_TODO',
  payload: { text }
});

// 2. Reducer (纯函数)
const todosReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { text: action.payload.text, completed: false }];
    default:
      return state;
  }
};

// 3. Store
const store = createStore(todosReducer);

// 4. View (React组件连接)
connect(mapStateToProps, mapDispatchToProps)(TodoList);

6. MobX响应式状态

class TodoStore {
  @observable todos = [];
  
  @action addTodo(text) {
    this.todos.push({ text, completed: false });
  }
  
  @computed get completedCount() {
    return this.todos.filter(todo => todo.completed).length;
  }
}

// 组件中自动响应
@observer
class TodoList extends React.Component {
  render() {
    return (
      <div>
        {store.todos.map(todo => (
          <TodoItem key={todo.id} todo={todo} />
        ))}
      </div>
    );
  }
}

🏗️ 架构模式

7. 微前端架构

将大型应用拆分为多个独立部署的微应用。

// 主应用 shell
class MainApp extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <header>主应用头部</header>
      <micro-app-a></micro-app-a>
      <micro-app-b></micro-app-b>
    `;
  }
}

// 微应用 A
class MicroAppA extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<div>微应用A的内容</div>`;
  }
}

customElements.define('micro-app-a', MicroAppA);

8. 领域驱动设计(DDD)在前端

// 领域对象
class ShoppingCart {
  constructor() {
    this.items = [];
    this.total = 0;
  }
  
  addItem(product, quantity) {
    const existingItem = this.items.find(item => item.product.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      this.items.push({ product, quantity });
    }
    
    this.calculateTotal();
  }
  
  calculateTotal() {
    this.total = this.items.reduce((sum, item) => {
      return sum + (item.product.price * item.quantity);
    }, 0);
  }
  
  // 业务规则
  canCheckout() {
    return this.items.length > 0 && this.total > 0;
  }
}

🎨 UI模式

9. 复合组件模式

创建一组协同工作的组件。

// 复合组件示例:标签页
const Tabs = ({ children }) => {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <div className="tabs">
      <div className="tab-list">
        {React.Children.map(children, (child, index) => (
          <TabHeader
            isActive={index === activeIndex}
            onClick={() => setActiveIndex(index)}
          >
            {child.props.title}
          </TabHeader>
        ))}
      </div>
      <div className="tab-content">
        {React.Children.toArray(children)[activeIndex]}
      </div>
    </div>
  );
};

// 子组件
const Tab = ({ children }) => <div>{children}</div>;

// 使用
<Tabs>
  <Tab title="首页">首页内容</Tab>
  <Tab title="关于">关于内容</Tab>
</Tabs>

10. 受控/非受控组件模式

// 受控组件
function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input 
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

// 非受控组件(通过ref访问)
function UncontrolledInput() {
  const inputRef = useRef();
  
  const handleSubmit = () => {
    console.log(inputRef.current.value);
  };
  
  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleSubmit}>提交</button>
    </>
  );
}

🔧 性能优化模式

11. 虚拟滚动

class VirtualScroll extends React.Component {
  state = { scrollTop: 0 };
  
  handleScroll = (e) => {
    this.setState({ scrollTop: e.target.scrollTop });
  };
  
  render() {
    const { items, itemHeight, visibleHeight } = this.props;
    const { scrollTop } = this.state;
    
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(
      startIndex + Math.ceil(visibleHeight / itemHeight) + 2,
      items.length
    );
    
    const visibleItems = items.slice(startIndex, endIndex);
    const offsetY = startIndex * itemHeight;
    
    return (
      <div 
        className="scroll-container"
        onScroll={this.handleScroll}
        style={{ height: visibleHeight }}
      >
        <div style={{ height: items.length * itemHeight }}>
          <div style={{ transform: `translateY(${offsetY}px)` }}>
            {visibleItems.map(item => (
              <div key={item.id} style={{ height: itemHeight }}>
                {item.content}
              </div>
            ))}
          </div>
        </div>
      </div>
    );
  }
}

12. 懒加载/代码分割

// React.lazy + Suspense
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <LazyComponent />
    </Suspense>
  );
}

// 图片懒加载
const LazyImage = ({ src, alt }) => {
  const [isLoaded, setIsLoaded] = useState(false);
  const imgRef = useRef();
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && !isLoaded) {
          imgRef.current.src = src;
          setIsLoaded(true);
        }
      },
      { threshold: 0.1 }
    );
    
    observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, [src, isLoaded]);
  
  return (
    <img
      ref={imgRef}
      alt={alt}
      style={{ opacity: isLoaded ? 1 : 0.3 }}
    />
  );
};

🚀 异步处理模式

13. Promise模式链

// Promise链式操作
class ApiService {
  async fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
      try {
        const response = await fetch(url);
        return await response.json();
      } catch (error) {
        if (i === retries - 1) throw error;
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
      }
    }
  }
  
  // 并行请求
  async fetchMultiple(urls) {
    return Promise.all(
      urls.map(url => this.fetchWithRetry(url))
    );
  }
  
  // 批量处理
  async batchProcess(items, batchSize = 10, processor) {
    const batches = [];
    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize));
    }
    
    const results = [];
    for (const batch of batches) {
      const batchResults = await Promise.all(
        batch.map(item => processor(item))
      );
      results.push(...batchResults);
    }
    
    return results;
  }
}

14. 观察者/发布订阅模式

class EventBus {
  constructor() {
    this.events = new Map();
  }
  
  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(callback);
  }
  
  off(event, callback) {
    if (this.events.has(event)) {
      const callbacks = this.events.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }
  
  emit(event, data) {
    if (this.events.has(event)) {
      this.events.get(event).forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`Error in event ${event}:`, error);
        }
      });
    }
  }
  
  once(event, callback) {
    const onceCallback = (data) => {
      callback(data);
      this.off(event, onceCallback);
    };
    this.on(event, onceCallback);
  }
}

// 使用
const bus = new EventBus();
bus.on('user-login', (user) => {
  console.log('用户登录:', user);
});

🧩 组合模式

15. 策略模式与组合

// 表单验证策略
const validationStrategies = {
  required: (value) => ({
    isValid: !!value,
    message: '此字段为必填项'
  }),
  
  email: (value) => ({
    isValid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    message: '请输入有效的邮箱地址'
  }),
  
  minLength: (value, min) => ({
    isValid: value.length >= min,
    message: `长度至少为${min}个字符`
  }),
  
  custom: (value, validator) => validator(value)
};

class FormValidator {
  constructor(strategies = validationStrategies) {
    this.strategies = strategies;
  }
  
  validate(field, value, rules) {
    const errors = [];
    
    rules.forEach(rule => {
      const { type, params = [], message } = rule;
      const strategy = this.strategies[type];
      
      if (strategy) {
        const result = strategy(value, ...params);
        if (!result.isValid) {
          errors.push(message || result.message);
        }
      }
    });
    
    return errors;
  }
}

// 使用
const validator = new FormValidator();
const errors = validator.validate('email', 'test@', [
  { type: 'required' },
  { type: 'email' }
]);

16. 装饰器模式(ES7)

// 类装饰器
function logMethodCalls(target) {
  const methods = Object.getOwnPropertyNames(target.prototype);
  
  methods.forEach(methodName => {
    if (methodName !== 'constructor') {
      const originalMethod = target.prototype[methodName];
      
      target.prototype[methodName] = function(...args) {
        console.log(`调用 ${methodName},参数:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`返回值:`, result);
        return result;
      };
    }
  });
  
  return target;
}

// 方法装饰器
function debounce(delay = 300) {
  return function(target, key, descriptor) {
    let timeout;
    const original = descriptor.value;
    
    descriptor.value = function(...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        original.apply(this, args);
      }, delay);
    };
    
    return descriptor;
  };
}

// 使用
@logMethodCalls
class ApiService {
  @debounce(500)
  search(query) {
    return fetch(`/api/search?q=${query}`);
  }
}

📊 前端设计模式选择指南

场景推荐模式优点
复用UI逻辑HOC/Render Props逻辑复用,组件解耦
状态共享Context/Redux/MobX状态管理集中化
性能优化虚拟滚动/懒加载减少渲染压力
表单处理受控组件 + 策略模式验证灵活,状态可控
异步操作Promise链 + 错误边界错误处理完整
组件通信事件总线/发布订阅跨组件通信解耦
复杂UI复合组件/插槽组件结构清晰

🎯 最佳实践建议

  1. 保持组件单一职责:每个组件只做一件事
  2. 优先使用函数组件+Hook:代码更简洁,逻辑更清晰
  3. 合理使用设计模式:不要过度设计,简单的场景用简单方案
  4. 关注性能:使用React.memo、useMemo、useCallback等优化
  5. 类型安全:TypeScript能帮助避免很多设计错误
  6. 测试友好:设计模式应便于单元测试

📚 现代前端框架内置模式

React特性对应的模式:

  • Hooks → 自定义Hook模式
  • Context → Provider模式
  • Suspense → 异步加载模式
  • Portal → 弹层/模态框模式

Vue特性对应的模式:

  • Mixins → 混入模式
  • Slots → 插槽模式
  • Composables → 组合式函数模式

这些设计模式在前端开发中非常实用,能够帮助你构建更可维护、可扩展的应用程序。