前端的设计模式?我觉得90%都是在过度设计!

最近Code Review的时候,我看到我们组一个很聪明的年轻同事,用观察者模式,写了一个极其复杂的全局状态订阅系统,就为了在一个组件里,响应另一个不相关的组件的点击事件。

比较常见的场景:点击 Button 组件,让 Panel 组件打印日志或显示提示,具体伪代码👇:

js 复制代码
// observer.js
class Observer {
  constructor() {
    this.subscribers = [];
  }

  subscribe(fn) {
    this.subscribers.push(fn);
  }

  unsubscribe(fn) {
    this.subscribers = this.subscribers.filter(sub => sub !== fn);
  }

  notify(data) {
    this.subscribers.forEach(fn => fn(data));
  }
}

// 全局状态中心(相当于单例)
export const globalClickObserver = new Observer();
jsx 复制代码
// Button.jsx
import React from "react";
import { globalClickObserver } from "./observer";

export default function Button() {
  const handleClick = () => {
    console.log("Button clicked");
    globalClickObserver.notify({ source: "Button", payload: "Hello Panel" });
  };

  return <button onClick={handleClick}>Click</button>;
}
jsx 复制代码
// Panel.jsx
import React, { useEffect } from "react";
import { globalClickObserver } from "./observer";

export default function Panel() {
  useEffect(() => {
    const subscriber = (data) => {
      if (data.source === "Button") {
        console.log("event:", data.payload);
      }
    };

    globalClickObserver.subscribe(subscriber);
    return () => globalClickObserver.unsubscribe(subscriber);
  }, []);

  return <div>I'm Panel</div>;
}

我把他叫过来,问他为什么不直接用一个简单的Event Bus(比如mitt),或者干脆用Zustand这样的状态管理器。

他说:"我觉得用设计模式,代码的扩展性会更好,也显得更高级😂。"

这个瞬间,让我下定决心,想聊聊这个话题:

在现代前端开发(尤其是React/Vue)中,我们挂在嘴边的那些经典设计模式,90%都是在过度设计。

在我开喷之前,请允许我澄清:我反对的不是设计思想,比如 高内聚低耦合单一职责。我反对的是,把那些20年前为Java/C++总结的、沉重的、面向对象的大招,生搬硬套到我们现代前端的开发范式里。


我们为什么会陷入设计模式的陷阱?

曾几何时,我也曾是设计模式的忠实信徒。热衷于在代码里寻找应用工厂模式、策略模式的场景。

我们之所以会这样,我觉得原因有二:

为了应对面试

设计模式是前端面试八股文里的重灾区。为了通过面试,我们不得不去背诵它们的定义和用法,这就导致了一种为了应考的惯性思维。

看起来牛皮🤷‍♂️

我们总觉得,能说出几个设计模式的名字,能把它们用在代码里,就代表自己的水平更高。仿佛不说个单例、不聊个装饰器,就体现不出自己的资深。


有哪些水土不服的设计模式?

我们来看几个在前端领域最常被滥用的经典模式。

单例模式

经典写法 :搞一个class,一个私有构造函数,再加一个getInstance的静态方法,防止被多次new

我的吐槽点别闹了,我们有ES6模块!!!

前端的原生模式 :JavaScript的import/export机制,天生就是单例的。你export一个实例,在所有地方import它,它从始至终就是同一个实例。

javascript 复制代码
// a.js
class MyService { /* ... */ }
// 导出一个实例
export const myServiceInstance = new MyService();

// b.js
import { myServiceInstance } from './a.js';
// c.js
import { myServiceInstance } from './a.js';
// b.js和c.js里的myServiceInstance,是同一个东西

为了实现单例,而去手写一个Singleton类,在现代前端里,属于(省略一万字...)。

工厂模式

写法 :写一个create函数,根据传入的typenew出不同的类的实例 ?。

在React/Vue里,我们有比工厂更强大、更直观的武器------组件 。你根本不需要一个create函数,你只需要一个组件 ,通过props来决定它的形态和行为。

jsx 复制代码
// 你不需要一个 createButton 的工厂
// 你只需要一个 Button 组件
function Button({ kind, ...props }) {
  if (kind === 'icon') {
    return <IconButton {...props} />;
  }
  if (kind === 'text') {
    return <TextButton {...props} />;
  }
  return <PrimaryButton {...props} />;
}

用组件思维去思考,比工厂思维更符合现代前端的直觉。

观察者模式

写法 :维护一个订阅者列表(subscribers),提供subscribeunsubscribenotify方法?

我的吐槽点是你的框架自带的响应式系统,比你手写的强一百倍。

React的useState/useEffect,Vue的ref/watch,它们本身就是更高阶、更强大的响应式系统,是观察者模式的终极体现。状态(被观察者)变化,UI(观察者)自动更新。你为什么要去手写一个简陋版的伪响应式,而不用框架自带的、经过千锤百炼的完整经验呢?


那剩下 10%有用的,是什么?

我喷了90%,那剩下10%依然有价值的是什么?在我看来,是一些设计思想,而不是具体的什么大招。

发布/订阅模式 (Pub/Sub)

它和观察者模式很像,但更解耦。当两个完全不相关的组件需要通信,而你又不想为此引入一个全局状态库时,一个轻量级的事件总线 (Event Bus)或者 mitt,就非常有用。

javascript 复制代码
// pubsub.js
class PubSub {
  constructor() {
    this.events = {}; // 存储事件和对应的订阅者回调
  }

  // 订阅
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return () => this.unsubscribe(event, callback); // 返回取消订阅函数
  }

  // 取消订阅
  unsubscribe(event, callback) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(cb => cb !== callback);
  }

  // 发布
  publish(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(data));
  }
}

// 导出一个全局单例
export const pubsub = new PubSub();

策略模式 : 这个模式的核心思想------将不同的算法封装起来,使它们可以互相替换------在前端依然非常闪光。它能帮助我们写出更优雅、更易扩展的代码,用来代替冗长的if/elseswitch

javascript 复制代码
// 比如,处理不同类型的用户折扣
const strategies = {
  'normal': (price) => price,
  'vip': (price) => price * 0.8,
  'svip': (price) => price * 0.6,
};

function calculatePrice(userType, price) {
  return strategies[userType](price);
}

你看,这里没有class,没有那么复杂的逻辑,但它蕴含了策略模式的思想。


作为组长,当我在Code Review 里看到一个同事用了工厂模式时,我不会觉得他很牛逼。我反而会警惕:他是不是为了炫技?而选择了一个更复杂的方案?我们能不能用一个简单的React组件,就把这事儿给干了?

现代前端框架,已经为我们内建了一套非常优秀、非常自洽的设计模式。组件是工厂,Hooks是装饰器/策略,响应式系统是观察者。

你的目标,不是写出能套上某个设计模式名字的代码,而是写出简单、清晰、易于维护的代码。在前端,后者往往比前者重要得多🤷‍♂️。

相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax