前端设计模式

55 阅读12分钟

🎯 设计模式

设计模式是在特定上下文中,针对反复出现的软件设计问题,总结出的可复用的解决方案模板。

设计模式 = 前人踩坑后留下的“通用解法套路”

设计模式的核心思想是把会变的部分从不变的部分中分离出来,让代码更易扩展、维护、组合、复用。

设计模式的本质就是为了管理变化,当项目增长到需要团队协作、可复用组件、可配置业务逻辑时,设计模式就开始发挥价值了。

📌 设计模式三大分类

1. 创建型模式

关注点:对象是“怎么被创建出来的”, 创建型模式的核心不是“new”,而是控制变化的创建过程

创建型模式的核心目标:

  • 隔离创建逻辑
  • 控制实例化过程
  • 解耦“用什么” vs “怎么 new”

常见模式:

  • Factory(工厂)⭐⭐⭐⭐
  • Abstract Factory(抽象工厂)
  • Singleton(单例)⭐⭐⭐
  • Builder(建造者)⭐⭐
  • Prototype(原型)

2. 结构型模式

关注点:对象/组件如何组合成更大的结构,结构型模式解决的是:“东西怎么拼起来不乱”

核心目标:

  • 用组合代替继承
  • 保持结构灵活
  • 避免强耦合

常见模式:

  • Decorator(装饰者)⭐⭐⭐⭐⭐(HOC / Hooks)
  • Composite(组合)⭐⭐⭐⭐⭐(组件树)
  • Adapter(适配器)⭐⭐⭐(接口适配)
  • Facade(外观)
  • Proxy(代理)

3. 行为型模式

关注点:对象之间如何通信、如何协作,行为型模式解决的是:“逻辑怎么跑才不炸”

核心目标:

  • 解耦行为调用
  • 封装变化的行为
  • 管理复杂流程

常见模式:

  • Strategy(策略)⭐⭐⭐⭐⭐
  • Observer(观察者)⭐⭐⭐⭐⭐
  • Command(命令)⭐⭐⭐
  • State(状态)⭐⭐⭐⭐
  • Iterator(迭代器)

📌 设计模式六大原则

1. 单一职责原则

❌ 错误理解:“只能写一个函数” 或者 “文件越小越好”

✅ 正确理解:变化原因只有一个,一个模块只负责一件事

👉 前端关注的“逻辑和渲染分离”,就是 SRP 的直接应用

2. 开闭原则 🔥🔥🔥

✅正确理解:对扩展开放,对修改关闭

考核重点:新需求来了,是加代码,还是改旧代码

👉前端典型实践:策略模式、插件化、配置驱动

❌ 反面教材:if else 嵌套

if(type==='A'){}
else if(type==='B'){} 

3. 里氏替换原则

✅正确理解:子类必须能替换父类,并且行为不破坏正确性

简单判断法:用父类的地方,换成子类,程序还能不能正常跑

📌 前端场景:组件继承(不推荐)、接口实现(TS interface)

4. 接口隔离原则

✅正确理解:不要强迫客户端依赖它不需要的接口

📌 前端体现:Props 精简、Hook 拆分、小而专的接口

❌ 错误:一个接口中包含太多当前不需要的字段

interface BigComponentProps {
  a: string
  b: string
  c: string
} 

✅ 正确:

interface AProps { a: string }
interface BProps { b: string } 

5. 依赖倒置原则

✅正确理解:依赖抽象,而不是依赖具体实现

📌 人话:高层模块不该知道底层细节

前端场景:依赖接口而不是 class、注入 service、Context / Provider

function useValidator(strategy: ValidationStrategy) {} 

6. 迪米特法则(最少知道原则)

✅正确理解:一个对象应尽量少地了解其他对象

📌 前端表现:不跨层访问状态、不 props.xxx.yyy.zzz、不直接操作别人的内部结构

📌 设计模式与React

1. 策略模式

🧠 本质:将算法封装成独立对象,可以在运行时动态切换。主要思想是把变化抽离,让逻辑可替换

前端最常见的复杂代码形态:

if (conditionA) { 
    doA() 
} else if (conditionB) { 
    doB() 
} else if (conditionC) { 
    doC() 
}

策略模式把条件判断变成组合规则。

🧠 模式结构(简化版)

Context(上下文) 
└── Strategy(策略接口) 
        ├── ConcreteStrategyA 
        ├── ConcreteStrategyB 
        └── ConcreteStrategyC

🧪 React 语境映射

角色React
ContextHook/容器组件
Strategy接口/函数
ConcreteStrategy独立模块/实现

🌰 React 实战案例:多类型内容渲染

  1. 定义策略接口
export interface RenderStrategy {
  render(value: string): JSX.Element
}

2. 具体策略实现

export const TextStrategy: RenderStrategy = {
  render: value => <p>{value}</p>
}

export const ImageStrategy: RenderStrategy = {
  render: value => <img src={value} />
}

export const LinkStrategy: RenderStrategy = {
  render: value => <a href={value}>{value}</a>
}

3. 上下文(Context)使用策略

const strategyMap: Record<string, RenderStrategy> = {
  text: TextStrategy,
  image: ImageStrategy,
  link: LinkStrategy,
}
export function Content({ type, value }: Props) {
  const strategy = strategyMap[type]
  return strategy ? strategy.render(value) : null
}

😊 优点:

  • 消除条件分支
  • 扩展成本低
  • 逻辑清晰

😭 缺点:

  • 策略数量可能增多
  • 需要一定设计成本

❌ 以下情况不能使用策略模式:

  • 逻辑永远不会变化
  • 只有 1 种实现
  • 为了“显得高级”而使用

2. 简单工厂模式

🧠 本质:把创建对象的逻辑抽离到一个地方,不让调用方写一堆 if/else。(封装创建逻辑)由一个工厂类(或函数)根据条件创建并返回不同的对象实例,调用方不关心对象如何创建,只关心“我要用哪个”。

模式结构:

Factory(工厂) 
   └── 根据参数创建不同产品 
Product(抽象产品) 
ConcreteProductA 
ConcreteProductB 
ConcreteProductC
角色React
Product接口 / 抽象类型
ConcreteProduct具体实现
Factory普通函数/类

前端典型痛点:

if (type === 'email') return new EmailValidator()
 if (type === 'password') return new PasswordValidator() 

✅ 改造成简单工厂模式

class ValidatorFactory {
  static create(type: string) {
    switch (type) {
      case 'email':
        return new EmailStrategy()
      case 'password':
        return new PasswordStrategy()
    }
  }
} 

调用方:

const validator = ValidatorFactory.create('email') 

😊 好处:

  • 创建逻辑集中
  • 新类型只改工厂,不改调用逻辑

⚠️ 工厂 + 策略

核心思想:工厂模式负责“创建谁”,策略模式负责“怎么用”。

export class ValidatorFactory {
  static create(type: ValidatorType): Validator {
    const map = {
      email: EmailValidator,
      password: PasswordValidator,
    }
    return new map[type]()
  }
}
const validator = ValidatorFactory.create(type)
validator.validate(value)

😊 优点

  • 创建逻辑集中
  • 调用方解耦
  • 易于维护
  • 适合中小规模扩展

❌ 缺点

  • 工厂本身会变胖
  • 新增类型仍需修改工厂
  • 违反严格意义的开闭原则

3. 观察者模式

🧠 本质:当一个对象改变状态,订阅它的其他对象都会自动得到通知。定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。

发布-订阅模式的基本实现,把“谁关心”和“谁改变”解耦。

💡 前端典型场景:

  • 表单 / UI 状态变化通知
  • WebSocket / SSE 消息分发
  • Redux / Zustand / MobX 状态订阅
  • 自定义事件系统 / Plugin 系统

🧠 核心结构

Subject(被观察者) 
   └── attach(observer) 
   └── detach(observer) 
   └── notify() 

Observer(观察者) 
   └── update()

React 语境映射

角色React
Subject数据源 / 状态容器 / WebSocket
Observer组件 / Hook / 回调函数

不解藕的情况下:一旦新增订阅者,需要改原函数

function changeState(newValue) { 
   doSomething() 
   doSomethingElse() 
   updateUI() 
}

React 自定义事件订阅系统

  1. 定义subject
class EventEmitter { 
    private listeners: Record<string, Callback[]> = {} 
    on(event: string, cb: Callback) { 
        if (!this.listeners[event]) this.listeners[event] = [] 
        this.listeners[event].push(cb) 
    } 
    
    off(event: string, cb: Callback) { 
        this.listeners[event] = this.listeners[event]?.filter(fn => fn !== cb) 
    } 

    emit(event: string, data?: any) { 
        this.listeners[event]?.forEach(fn => fn(data)) 
    } 
} 
export const emitter = new EventEmitter()

2. React 组件作为观察者

mport { useEffect, useState } from 'react' 
import { emitter } from './EventEmitter'
 
export function UserStatus() { 
    const [status, setStatus] = useState('offline') 
    
    useEffect(() => { 
        const callback = (newStatus: string) => setStatus(newStatus) 
        emitter.on('statusChange', callback) 
        return () => emitter.off('statusChange', callback) 
    }, []) 
    
    return <p>用户状态: {status}</p> 
}

3. 发布状态

emitter.emit('statusChange', 'online')

结合 React Hook 封装

这样每个组件都可以直接调用 useEvent('statusChange'),不关心底层实现

function useEvent<T>(event: string) { 
    const [data, setData] = useState<T | null>(null) 

    useEffect(() => { 
        const cb = (payload: T) => setData(payload) 
        emitter.on(event, cb) 
        return () => emitter.off(event, cb) 
    }, [event]) 

    return data 
}

✅优点

  • 高度解耦:发布者不关心订阅者
  • 扩展性好:新增订阅者无需改动发布者
  • 支持动态订阅

❌缺点

  • 订阅者过多时可能性能下降
  • 调试链条复杂
  • 生命周期管理不当可能内存泄漏

4. 装饰者模式

🧠 本质:不修改原对象的情况下给对象增加额外的行为。动态地给一个对象添加额外的职责,而不改变其原有结构。

给组件 / 对象贴“外挂”,又不动原本代码

💡前端场景:

  • UI 组件增加 Loading / Error / 权限控制
  • 高阶组件(HOC)
  • Hook 封装的逻辑增强
  • 日志 / 埋点 / 域名 / 配置增强

不使用装饰者模式:

function Button(props) { 
    if (props.loading) return <Spinner /> 
    if (props.disabled) return <DisabledButton /> 
    return <button>{props.label}</button> 
}

🎯 模式结构

Component(抽象对象) 
ConcreteComponent(具体对象) 
Decorator(抽象装饰器) 
ConcreteDecorator(具体装饰器)
角色React
Component原始组件
ConcreteComponent具体实现组件
Decorator高阶组件 / Hook 封装
ConcreteDecorator具体增强逻辑

React Button 增强

有一个基础组件 Button,但有天要加 Loading:

function withLoading(Component) {
  return function Wrapped(props) {
    return props.loading ? <Spinner /> : <Component {...props} />
  }
}

const ButtonWithLoading = withLoading(Button) 

React Hooks 封装实现装饰者

function useLoading(initial = false) {
  const [loading, setLoading] = useState(initial)

  const wrap = (fn: () => void) => () => {
    setLoading(true)
    fn()
    setLoading(false)
  }

  return { loading, wrap }
}
export function SubmitButton({ onClick }: { onClick: () => void }) {
  const { loading, wrap } = useLoading()
  return <Button label="提交" onClick={wrap(onClick)} />
}

✅优点

  • 动态增强组件
  • 不破坏原有组件
  • 可复用、可组合
  • 高扩展性

❌缺点

  • HOC 嵌套过深容易调试困难
  • Hook 封装过多可能逻辑分散
  • 需要规范命名 / props

5. 组合模式

🧠 本质:把一组对象当成单个对象来使用,形成树状结构。将对象组合成树形结构以表示“部分-整体”层次结构,使客户端可以统一对待单个对象和组合对象。

无论是叶子还是容器,调用方式一样

💡 前端例子:

  • UI 组件树(Layout / Form / Menu / Tree)
  • 树状数据渲染(文件管理 / 分类)
  • 菜单系统 / 多层嵌套

模型结构


Root
 ├── Child 1
 ├── Child 2
      ├── Child 2.1
      └── Child 2.2 

每个节点既可以是 叶子,也可以是 容器。

在 React:

<Layout>
  <Header />
  <Main>
    <Sidebar />
    <Content />
  </Main>
</Layout> 

组合设计就是把“节点 + 容器” 当成统一行为。

React 多级菜单组件

  1. 叶子组件
function MenuLeaf({ item }: { item: MenuItemType }) { 
    return <li>{item.label}</li> 
}

2. 容器组件

function MenuComposite({ items }: { items: MenuItemType[] }) {
  return (
    <ul>
      {items.map(item => 
        item.children ? (
          <li key={item.key}>
            {item.label}
            <MenuComposite items={item.children} />
          </li>
        ) : (
          <MenuLeaf key={item.key} item={item} />
        )
      )}
    </ul>
  )
}

3. 调用

const menuData: MenuItemType[] = [ 
    { key: 'home', label: '首页' }, 
    { key: 'system', label: '系统管理', children: [ 
        { key: 'user', label: '用户管理' }, 
        { key: 'role', label: '角色管理' } 
    ] } 
] 
<MenuComposite items={menuData} />
  • 任意层级都统一处理
  • 叶子和组合节点调用方式一致
  • 易扩展,新增层级无需改核心逻辑

✅优点

  • 层级处理统一
  • 易扩展
  • 树状结构天然适配递归
  • 菜单、树、布局

❌缺点

  • 递归渲染层级过深可能性能问题
  • 状态管理需注意(展开/选中等)

6. 命令模式

🧠 本质:将执行动作封装为对象,使得命令可存储、撤销、重做。将一个请求封装为一个对象,从而使你可以用不同的请求、队列或日志来参数化客户端;同时支持可撤销操作。

把动作变成对象,让调用者不关心具体实现

💡 前端例子

  • 富文本编辑器(撤销/重做)
  • 可撤销操作(Canvas / 图表编辑)
  • VSCode 插件 / IDE 命令
  • 按钮动作统一管理

不实用命令模式:

function handleClick(action: string) {
  if (action === 'save') save()
  if (action === 'delete') deleteItem()
  if (action === 'copy') copyItem()
}

🧠 模式结构

Command(抽象命令) 
ConcreteCommand(具体命令) 
Invoker(调用者) 
Receiver(执行者) 

React 按钮操作命令

1. 命令执行者

export class TextReceiver { 
    private text = '' 
    setText(newText: string) { 
        this.text = newText 
        console.log('文本更新为:', this.text) 
    } 
    getText() { return this.text } 
}

2. 具体命令
export class UpdateTextCommand implements Command {
  constructor(private receiver: TextReceiver, private newText: string, private prevText?: string) {}
  
  execute() {
    this.prevText = this.receiver.getText()
    this.receiver.setText(this.newText)
  }

  undo() {
    if (this.prevText !== undefined) this.receiver.setText(this.prevText)
  }
}

3. 调用者
import { useState } from 'react' 

export function CommandDemo() { 
    const [receiver] = useState(() => new TextReceiver()) 
    const [history, setHistory] = useState<Command[]>([]) 
    const executeCommand = (cmd: Command) => { 
        cmd.execute() 
        setHistory(prev => [...prev, cmd]) 
    } 
    const undo = () => { 
       const last = history[history.length - 1] 
       last?.undo?.() 
       setHistory(prev => prev.slice(0, -1)) 
    } 
    
    return ( 
        <div> 
           <button onClick={() => executeCommand(new UpdateTextCommand(receiver, 'Hello'))}>
              写 Hello
           </button> 
           <button onClick={() => executeCommand(new UpdateTextCommand(receiver, 'World'))}>
              写 World
           </button> 
           <button onClick={undo}>撤销</button> 
        </div> 
    ) 
} 

✅优点

  • 调用者与执行者解耦
  • 支持撤销/重做
  • 支持操作队列 / 日志
  • 高可扩展性

❌缺点

  • 命令对象数量可能增多
  • 设计稍微复杂

7. 状态模式

🧠 本质:明确把状态和行为分开,把状态的变化关系写成表或图。允许一个对象在其内部状态改变时改变其行为,对象看起来像改变了其类。

把状态和状态对应的行为封装起来,让对象根据状态自己切换行为

💡 典型场景:

  • 表单多步骤流程(填写 → 提交 → 成功/失败)
  • 复杂组件的 UI 状态切换(loading / error / empty / success)
  • 动画/交互状态管理
  • 游戏或 Canvas UI 状态管理

🧠 模式结构

Context(上下文对象) 
    └── State(抽象状态接口) 
         ├── ConcreteStateA 
         ├── ConcreteStateB 
         └── ConcreteStateC 

表单状态机


interface FormState {
  submit: () => void
  render: () => JSX.Element
}

1. 具体状态实现
class IdleState implements FormState {
  constructor(private context: FormContext) {}
  submit() {
    this.context.setState(new SubmittingState(this.context))
  }
  render() {
    return <button>提交</button>
  }
}

class SubmittingState implements FormState {
  constructor(private context: FormContext) {}
  submit() {
    // 正在提交,不可再提交
  }
  render() {
    return <button disabled>提交中...</button>
  }
}

class SuccessState implements FormState {
  constructor(private context: FormContext) {}
  submit() {
    // 提交完成,不可再提交
  }
  render() {
    return <p>提交成功 🎉</p>
  }
}

2. Context(状态容器)
class FormContext {
  private state: FormState
  constructor() {
    this.state = new IdleState(this)
  }
  setState(state: FormState) {
    this.state = state
  }
  submit() {
    this.state.submit()
  }
  render() {
    return this.state.render()
  }
}

3. React UI 使用
export function FormComponent() {
  const [context] = useState(() => new FormContext())

  return (
    <div>
      {context.render()}
      <button onClick={() => context.submit()}>提交</button>
    </div>
  )
}

✅优点

  • 消除条件分支
  • 状态逻辑集中封装
  • 易于扩展新状态
  • 面试高频题:表单 / 流程 / 组件状态

❌缺点

  • 状态类数量可能增加
  • 初期设计成本稍高
  • 对小型组件可能过度设计

📌 设计模式之间的关系

分类前端高频模式核心作用
创建型工厂模式、单例模式、建造者控制对象创建,封装变化
结构型装饰者、组合、适配器、代理、外观组织对象/组件结构,实现复用和解耦
行为型策略、观察者、状态、命令、迭代器管理对象间行为和通信,封装算法或操作
模式解决什么问题前端常见用途
Factory对象创建消除 if-else
Strategy多种行为可替换校验/渲染策略
Observer发布订阅事件 / 状态变化
Decorator动态增强HOC / 逻辑增强
Composite递归结构组件树结构
Command行为封装可撤销命令
State Machine状态转换UI/流程状态
  1. 原则导向
  • 模式不是单独存在的,它们都是原则(SRP、OCP、DIP 等)的实现工具
  • 组合使用是高级前端架构常态
  1. 模式组合实战
  • 工厂 + 策略 → 动态生成行为
  • 观察者 + 状态 → 状态变化驱动 UI
  • 命令 + 状态 / 装饰者 → 操作解耦 + UI 增强
  • 组合 + 策略 / 装饰者 → 树状结构灵活渲染

image.png