Theme / v4.9.0

Ponytail

讓 AI 編碼代理像最懶的資深工程師

實戰範例

實戰範例 006:減輕負重:以原生 API 替換龐大的第三方依賴

展示如何在前端專案中移除諸如 Lodash 等龐大且不必要的第三方函式庫,利用現代 JavaScript (ES6+) 原生方法達成相同目標。

實戰範例 006:減輕負重:以原生 API 替換龐大的第三方依賴

簡介與核心精神

在十年前的網頁開發環境中,JavaScript 語言本身的標準庫相對貧乏。那時,為了處理陣列過濾、物件深拷貝、防抖 (Debounce) 或字串操作,開發者幾乎將 Lodash、Underscore 等工具庫視為專案標配。

然而,隨著 ECMAScript 標準的快速演進(ES6、ES2015 及之後的版本),現代瀏覽器與 Node.js 環境已經內建了極其強大且語意化的原生 API。在 Ponytail 極簡主義開發理念中,「每一行引入的第三方依賴,都是未來的技術債」。過多的第三方依賴不僅會增加打包後的 Bundle Size(拖慢網頁載入速度),還會帶來版本衝突、安全性漏洞 (如著名的 left-pad 事件) 以及額外的維護成本。

本篇實戰範例將探討一個典型的前端過度依賴場景:在完全可以使用原生陣列方法與 ES11 (ES2020) 語法的情況下,依然盲目引入龐大的 Lodash 函式庫。我們將展示如何以「原生優先 (Native-First)」的思維,替換這些多餘的依賴,減輕專案的負重。

實際場景描述

我們正在開發一個資料視覺化的儀表板 (Dashboard) 應用程式。其中一個核心功能是接收後端傳來的原始數據陣列,過濾掉無效資料、對欄位進行排序、並將物件轉換成適合圖表渲染的格式。

一位習慣了舊時代開發模式的工程師,在建立專案的第一天就毫不猶豫地執行了 npm install lodash,並在程式碼中大量使用了 _.filter_.map_.orderBy_.get_.cloneDeep。隨著專案增長,團隊發現首屏載入時間越來越長,Webpack 的分析報告顯示,Lodash 佔據了極大比例的 Bundle Size。

原始的過度依賴代碼

這是一段極度依賴 Lodash 的資料處理邏輯。雖然程式碼看起來能夠運作,但為了幾個簡單的操作引入一整個外部函式庫,顯然是不符合極簡原則的。

// 檔案:src/utils/dataProcessor.js
import _ from 'lodash';

export class DataProcessor {
  /**
   * 處理原始的圖表資料
   * @param {Array} rawData - 來自後端的原始資料
   */
  processChartData(rawData) {
    // 防呆檢查
    if (_.isNil(rawData) || !_.isArray(rawData)) {
      return [];
    }

    // 1. 深拷貝原始資料以避免副作用 (Side Effects)
    const clonedData = _.cloneDeep(rawData);

    // 2. 過濾掉狀態不為 active 的資料
    const activeData = _.filter(clonedData, (item) => {
      return _.get(item, 'status') === 'active';
    });

    // 3. 轉換資料格式 (Mapping)
    const formattedData = _.map(activeData, (item) => {
      return {
        id: _.get(item, 'id'),
        // 處理深層嵌套且可能不存在的屬性
        userName: _.get(item, 'user.profile.name', 'Unknown User'),
        value: _.get(item, 'metrics.score', 0) * 100
      };
    });

    // 4. 根據數值由大到小排序
    const sortedData = _.orderBy(formattedData, ['value'], ['desc']);

    return sortedData;
  }
}

在這個例子中,_.isNil_.isArray_.filter_.map 等方法早已是 JavaScript 語言的原生功能;而 _.get 用來安全存取深層屬性的功能,在 Optional Chaining (?.) 與 Nullish Coalescing (??) 普及後也顯得多餘。此外,為了避免副作用而使用 _.cloneDeep 是一個非常昂貴的操作,在這個場景下,由於 map 本身就會回傳新陣列,深拷貝甚至是完全不必要的。

開發者與 AI 的對話記錄

為了移除這些不必要的龐然大物,開發者與 AI 代理展開了對話。

開發者

「AI,我們的前端效能稽核報告顯示 Bundle Size 太大了,Lodash 佔了很大一部分。我想改成按需引入 (Tree Shaking),例如 import get from 'lodash/get',你覺得這樣能解決問題嗎?」

AI (Ponytail 模式)

「按需引入只是治標不治本的創可貼。真正的問題在於,你根本不需要這塊創可貼。

仔細看看你的程式碼:為什麼你需要 _.isArray?原生就有 Array.isArray()。為什麼你需要 _.filter_.map?陣列的原生方法效能更好且不需額外引入。更誇張的是 _.get,現代 JavaScript 早就提供了 Optional Chaining (?.) 來完美解決安全取值的問題。」

開發者

「但那個 _.cloneDeep 總需要吧?前輩說這樣才能保證純函數 (Pure Function),不會不小心修改到傳進來的 rawData。」

AI (Ponytail 模式)

「這是一個常見的誤解。在你後續的處理中,原生的 .filter().map() 本來就會建立並回傳全新的陣列與物件。你一開始做的那次徹底的深拷貝不僅浪費 CPU 和記憶體,而且毫無意義。這就是盲目堆砌工具庫所帶來的效能災難。

讓我們揮舞 Ponytail 的極簡之劍,把這整個 import _ from 'lodash' 徹底砍掉。我們只用 2020 年以後的原生 JavaScript,寫出更短、更快、且零依賴的代碼。」

開發者

「好!那我們來重構,把 Lodash 從 package.json 裡面徹底拔掉!」

重構與優化:原生 API 的覺醒

我們將使用純粹的現代 JavaScript (ES6+) 原生方法來重寫這段邏輯。這不僅減輕了依賴,程式碼的可讀性與執行效能也得到了雙重提升。同時,我們加入 Agent Reach 提倡的嚴格型別與邊界防護。

// 檔案:src/utils/dataProcessor.js
// 完美!我們不再需要任何外部依賴

export class DataProcessor {
  /**
   * 處理原始的圖表資料
   * @param {Array} rawData - 來自後端的原始資料
   */
  processChartData(rawData) {
    // 原生防呆檢查:明確、快速、不依賴外部模組
    if (!rawData || !Array.isArray(rawData)) {
      console.warn("Invalid input to processChartData. Expected an array.");
      return [];
    }

    // 利用鍊式呼叫 (Chaining) 一氣呵成,無須預先 cloneDeep
    return rawData
      // 1. 原生 filter:只保留 active 資料
      .filter(item => item && item.status === 'active')
      
      // 2. 原生 map:轉換資料格式,並利用 Optional Chaining 與 Nullish Coalescing
      .map(item => ({
        id: item.id,
        // 使用 ?. 避免 TypeError,使用 ?? 提供預設值
        userName: item.user?.profile?.name ?? 'Unknown User',
        value: (item.metrics?.score ?? 0) * 100
      }))
      
      // 3. 原生 sort:根據 value 由大到小排序
      // 注意:原生 sort 會改變原陣列,但因為它是接在 map 產生的新陣列之後,所以完全安全。
      .sort((a, b) => b.value - a.value);
  }
}

重構亮點分析

  1. 消滅第三方依賴:完全移除了 Lodash,省下了數十 KB 的打包體積,減少了專案安全更新與相依性審查的成本。
  2. 語法現代化與簡潔化:用 item.user?.profile?.name ?? 'Unknown User' 取代了冗長且字串化的 _.get(item, 'user.profile.name', 'Unknown...')。這讓大部分現代 IDE 都能夠正確推斷並提供屬性補全 (Auto-completion)。
  3. 消除無意義的效能損耗:移除了盲目的 _.cloneDeep。由於 filtermap 本身就是不可變 (Immutable) 操作,直接鍊式呼叫既安全又高效。
  4. 原生陣列操作的直覺性:JavaScript 社群早已習慣原生陣列方法。使用 sort 搭配簡單的比較函式 (a, b) => b.value - a.value,比記憶 Lodash 特有的排序陣列語法更為直觀。

效益分析表格與解讀

這次「減法工程」帶來的改變不僅是程式碼行數的減少,更重要的是在專案健康度上取得了全面勝利。

評估維度 依賴 Lodash 的舊版本 原生 JavaScript 重構後 改善程度 / 解讀
外部依賴數量 1 (Lodash) 0 徹底歸零。免除了未來可能產生的版本衝突或資安弱點掃描警告。
Bundle Size (Minified) 增加 ~70KB (若全包) 增加 0KB 顯著縮短首屏載入時間,減少使用者的網路頻寬消耗。
執行效能 差 (無謂的深拷貝與封裝開銷) 優 (原生 C++ 引擎直接優化) 移除了 cloneDeep 後,處理大量陣列時的速度提升可達倍數以上。
程式碼長度 約 25 行 (冗長且宣告分散) 約 10 行 (優雅的鍊式呼叫) 高度濃縮。意圖明確,符合聲明式 (Declarative) 的撰寫風格。
IDE 支援度 弱 (以字串路徑取值無法享受型別檢查) 強 (可結合 TypeScript 提供完美提示) ?. 語法完全相容於 TypeScript,能輕易揪出屬性拼寫錯誤。

結論總結

在軟體工程中,「最容易維護的代碼,就是沒有被寫出來的代碼;最安全的第三方套件,就是不需要安裝的套件。」

Ponytail 的極簡主義不斷提醒我們,工具庫是為了解決特定時代的問題而生的。隨著平台與語言標準的進步,我們必須擁有重新審視並丟棄舊包袱的勇氣。透過擁抱現代原生 API,我們不僅讓程式碼變得更加輕盈、高效,也讓專案擺脫了被龐大依賴綁架的命運。這是一場向簡單與純粹致敬的重構之旅。