
最近Code Review的时候我看到我们组一个很聪明的年轻同事用观察者模式写了一个极其复杂的全局状态订阅系统就为了在一个组件里响应另一个不相关的组件的点击事件。比较常见的场景点击 Button 组件让 Panel 组件打印日志或显示提示具体伪代码:// observer.jsclassObserver{constructor(){this.subscribers[];}subscribe(fn){this.subscribers.push(fn);}unsubscribe(fn){this.subscribersthis.subscribers.filter(subsub!fn);}notify(data){this.subscribers.forEach(fnfn(data));}}// 全局状态中心相当于单例exportconstglobalClickObservernewObserver();// Button.jsximportReactfromreact;import{globalClickObserver}from./observer;exportdefaultfunctionButton(){consthandleClick(){console.log(Button clicked);globalClickObserver.notify({source:Button,payload:Hello Panel});};returnbuttononClick{handleClick}Click/button;}// Panel.jsximportReact,{useEffect}fromreact;import{globalClickObserver}from./observer;exportdefaultfunctionPanel(){useEffect((){constsubscriber(data){if(data.sourceButton){console.log(event:,data.payload);}};globalClickObserver.subscribe(subscriber);return()globalClickObserver.unsubscribe(subscriber);},[]);returndivIm Panel/div;}我把他叫过来问他为什么不直接用一个简单的Event Bus比如mitt或者干脆用Zustand这样的状态管理器。他说“我觉得用设计模式代码的扩展性会更好也显得更高级。”这个瞬间让我下定决心想聊聊这个话题在现代前端开发尤其是React/Vue中我们挂在嘴边的那些经典设计模式90%都是在过度设计。在我开喷之前请允许我澄清我反对的不是设计思想比如高内聚低耦合、单一职责。我反对的是把那些20年前为Java/C总结的、沉重的、面向对象的大招生搬硬套到我们现代前端的开发范式里。我们为什么会陷入设计模式的陷阱曾几何时我也曾是设计模式的忠实信徒。热衷于在代码里寻找应用工厂模式、策略模式的场景。我们之所以会这样我觉得原因有二为了应对面试设计模式是前端面试八股文里的重灾区。为了通过面试我们不得不去背诵它们的定义和用法这就导致了一种为了应考的惯性思维。看起来牛皮♂️我们总觉得能说出几个设计模式的名字能把它们用在代码里就代表自己的水平更高。仿佛不说个单例、不聊个装饰器就体现不出自己的资深。有哪些水土不服的设计模式我们来看几个在前端领域最常被滥用的经典模式。单例模式经典写法搞一个class一个私有构造函数再加一个getInstance的静态方法防止被多次new。我的吐槽点别闹了我们有ES6模块前端的原生模式JavaScript的import/export机制天生就是单例的。你export一个实例在所有地方import它它从始至终就是同一个实例。// a.jsclassMyService{/* ... */}// 导出一个实例exportconstmyServiceInstancenewMyService();// 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(kindicon){returnIconButton{...props}/;}if(kindtext){returnTextButton{...props}/;}returnPrimaryButton{...props}/;}用组件思维去思考比工厂思维更符合现代前端的直觉。观察者模式写法维护一个订阅者列表subscribers提供subscribe、unsubscribe和notify方法我的吐槽点是你的框架自带的响应式系统比你手写的强一百倍。React的useState/useEffectVue的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(cbcb!callback);}// 发布publish(event,data){if(!this.events[event])return;this.events[event].forEach(callbackcallback(data));}}// 导出一个全局单例exportconstpubsubnewPubSub();策略模式这个模式的核心思想——将不同的算法封装起来使它们可以互相替换——在前端依然非常闪光。它能帮助我们写出更优雅、更易扩展的代码用来代替冗长的if/else或switch。// 比如处理不同类型的用户折扣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是装饰器/策略响应式系统是观察者。你的目标不是写出能套上某个设计模式名字的代码而是写出简单、清晰、易于维护的代码。在前端后者往往比前者重要得多♂️。