当前位置: 首页 > news >正文

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

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

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

// observer.jsclassObserver{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));}}// 全局状态中心(相当于单例)exportconstglobalClickObserver=newObserver();
// Button.jsximportReactfrom"react";import{globalClickObserver}from"./observer";exportdefaultfunctionButton(){consthandleClick=()=>{console.log("Button clicked");globalClickObserver.notify({source:"Button",payload:"Hello Panel"});};return<buttononClick={handleClick}>Click</button>;}
// Panel.jsximportReact,{useEffect}from"react";import{globalClickObserver}from"./observer";exportdefaultfunctionPanel(){useEffect(()=>{constsubscriber=(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它,它从始至终就是同一个实例。

// a.jsclassMyService{/* ... */}// 导出一个实例exportconstmyServiceInstance=newMyService();// b.jsimport{myServiceInstance}from'./a.js';// c.jsimport{myServiceInstance}from'./a.js';// b.js和c.js里的myServiceInstance,是同一个东西

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

工厂模式

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

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

// 你不需要一个 createButton 的工厂// 你只需要一个 Button 组件functionButton({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,就非常有用。

// pubsub.jsclassPubSub{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));}}// 导出一个全局单例exportconstpubsub=newPubSub();

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

// 比如,处理不同类型的用户折扣conststrategies={'normal':(price)=>price,'vip':(price)=>price*0.8,'svip':(price)=>price*0.6,};functioncalculatePrice(userType,price){returnstrategies[userType](price);}

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


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

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

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

http://www.cnnetsun.cn/news/3763335.html

相关文章:

  • Windows端口占用排查全攻略:从netstat到PowerShell实战
  • 网络故障排查利器:tcpdump ARP抓包实战指南
  • 再读人月神话:AI 时代下的产品化与系统化
  • OpenCode 速通:19 万星,能自己操控浏览器的 AI 编程神器
  • 玉米生育期精准记录:从田间观测到农事决策的完整指南
  • 低成本开启 AI 布局,主流大模型商用接口稳定供应
  • 从终端现场出发,重新理解快消品牌增长—#纳宝科技刘行
  • Python批量图像位深度转换:从原理到工程实践
  • STP协议详解:从网络环路到稳定连接的生成树技术
  • Python Socket 常用代码汇总|TCP/UDP 服务端、客户端基础模板
  • MinIO IAM Policy配置全解析:从基础概念到高级权限管理实战
  • STC单片机烧录全攻略:从原理到实战,解决常见问题
  • 压敏电阻原理与应用:从防雷卫士到电路保护设计实战
  • C++ OpenCV图像处理:LUT查找表原理与实战性能优化
  • Nginx反向代理HTTPS服务报错排查:The plain HTTP request was sent to HTTPS port
  • 逻辑分析仪阈值电压硬核选购标准全解析|电平判决原理、多电压系统适配、阈值设置避坑实战指南
  • 基于Transformer的风电功率预测MATLAB实现
  • 企业级RAG技术:智能知识库的核心架构与优化实践
  • 终极指南:55项炉石传说插件功能全面解锁游戏新境界
  • 捷米特 JM-RS-WIFI 数传模块,智能仓储 AGV 小车串口无线通讯解决方案
  • 扣子变量传递失效真相(生产环境血泪调试实录):3类隐式类型转换陷阱正在 silently 毁掉你的自动化流程
  • 嵌入式开发实战:STM32按键消抖原理、状态机实现与RTOS应用
  • 大模型应用开发工程师:2026年高薪转行风口,小白也能抓住的机会!收藏必备!
  • BetterGI技术深度解析:5大核心技术构建原神自动化辅助框架
  • 实测:如何检测你的网站是否被 ChatGPT、豆包、Perplexity 引用?附 30 秒免费工具
  • STM32独立看门狗(IWDG)原理、配置与实战设计指南
  • OpenSpeedy终极指南:免费开源游戏加速工具完整教程
  • Satechi Thunderbolt 5 CubeDock 评测:创新 SSD 扩展坞,性能出色但有局限
  • 手动安装 OpenAI Codex Windows 桌面版
  • Arduino开发工具链解析:从图形化编程到专业IDE的进阶指南