🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

前端的設計模式?我覺得90%都是在過度設計!

image.png

最近Code Review的時候,我看到我們組一個很聰明的年輕同事,用觀察者模式,寫了一個極其複雜的全球狀態訂閱系統,就為了在一個元件裡,響應另一個不相關的元件的點擊事件。

比較常見的場景:點擊 Button 元件,讓 Panel 元件打印日誌或顯示提示,具體偽代碼👇:

// 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();
// 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>;
}
// 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++總結的、沉重的、面向對象的大招,生搬硬套到我們現代前端的開發範式裡。


我們為什麼會陷入設計模式的陷阱?

image.png

曾幾何時,我也曾是設計模式的忠實信徒。熱衷於在代碼裡尋找應用工廠模式、策略模式的場景。

我們之所以會這樣,我覺得原因有二:

為了應對面試

設計模式是前端面試八股文裡的重災區。為了通過面試,我們不得不去背誦它們的定義和用法,這就導致了一種為了應考的慣性思維。

看起來牛皮🤷‍♂️

我們總覺得,能說出幾個設計模式的名字,能把它們用在代碼裡,就代表自己的水平更高。仿佛不說個單例、不聊個裝飾器,就體現不出自己的資深。


有哪些水土不服的設計模式?

我們來看幾個在前端領域最常被濫用的經典模式。

單例模式

經典寫法:搞一個class,一個私有構造函數,再加一個getInstance的靜態方法,防止被多次new

我的吐槽點別鬧了,我們有ES6模組!!!

前端的原生模式:JavaScript的import/export機制,天生就是單例的。你export一個實例,在所有地方import它,它從始至終就是同一個實例。

// 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來決定它的形態和行為。

// 你不需要一個 createButton 的工廠
// 你只需要一個 Button 元件
function Button({ kind, ...props }) {
  if (kind === 'icon') {
    return <IconButton {...props} />;
  }
  if (kind === 'text') {
    return <TextButton {...props} />;
  }
  return <PrimaryButton {...props} />;
}

用元件思維去思考,比工廠思維更符合現代前端的直覺。

觀察者模式

image.png

寫法:維護一個訂閱者列表(subscribers),提供subscribeunsubscribenotify方法?

我的吐槽點是你的框架自帶的響應式系統,比你手寫的強一百倍。

React的useState/useEffect,Vue的ref/watch,它們本身就是更高階、更強大的響應式系統,是觀察者模式的終極體現。狀態(被觀察者)變化,UI(觀察者)自動更新。你為什麼要去手寫一個簡陋版的偽響應式,而不用框架自帶的、經過千錘百鍛的完整經驗呢?


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

我噴了90%,那剩下10%依然有價值的是什麼?在我看來,是一些設計思想,而不是具體的什麼大招。

發佈/訂閱模式 (Pub/Sub)
它和觀察者模式很像,但更解耦。當兩個完全不相關的元件需要通信,而你又不想為此引入一個全球狀態庫時,一個輕量級的事件總線(Event Bus)或者 mitt,就非常有用。

// 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

// 比如,處理不同類型的用戶折扣
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是裝飾器/策略,響應式系統是觀察者。

你的目標,不是寫出能套上某個設計模式名字的代碼,而是寫出簡單、清晰、易於維護的代碼。在前端,後者往往比前者重要得多🤷‍♂️。


原文出處:https://juejin.cn/post/7554646886499680302


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝12   💬8   ❤️4
589
🥈
我愛JS
📝3   💬10   ❤️7
204
🥉
AppleLily
📝1   💬4   ❤️1
70
#5
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付