拖了好久的文,終於來寫。

這篇預設給熟悉Array.map()for loop的朋朋閱讀。

什麼情境下選擇改用Map

通常在從 server API 取得資料後、實際應用到前端之前,我會先進行資料轉換,調整成自己習慣的 key 命名及資料格式,並重組成更適合使用的資料結構。

當需求只是單純的一對一轉換時,整個流程前後都是以 array 為主,使用Array.map()就能很好地完成這件事。

不過當需求開始變成「一個 key 對應多筆資料」時,資料處理的重點變成「如何依照 key 累積資料」。這時我發現,使用 object 來暫存分組結果,並不能清楚表達我正在建立一個用來查找與分組的索引結構,因此改用Map,讓 code 本身更貼近實際的資料處理意圖。

當資料是一對一時:Array.map()

當從 server API 取得資料後,如果需求只是將資料轉換成前端習慣的格式,例如調整 key 命名、轉換欄位值或補上預設值,這類一對一的資料轉換,使用Array.map()就已經非常足夠。

例如,server 回傳的員工資料可能長這樣:

const employees = [{
  "eid": "202242", // 員工編號
  "did": "d01", // 部門 ID
  "fullname": "Tom Smith", // 員工全名
  "gender": 1 // 員工性別
}, {
  "eid": "202414",
  "did": "d02", 
  "fullname": "Chen Peiyu",
  "gender": 2
}, {
  "eid": "202505",
  "did": "d01", 
  "fullname": "Chang lin",
  "gender": 2
}];

在前端實際使用前,我通常會先做一次資料轉換:

const genderMap = {
  1: "male",
  2: "female"
};

const updatedEmployees = employees.map((emp) => ({
  "employeeId": emp.eid,
  "departmentId": emp.did,
  "fullname": emp.fullname,
  "gender": genderMap[emp.gender] ?? "unknown"
}));

在這種情境下,每一筆輸入資料都對應到一筆輸出資料,資料筆數前後保持一致,Array.map()能很清楚地表達「這一筆資料要被轉換成什麼樣子」。

也正因為如此,當需求僅僅是 key 的重命名或資料格式的調整時,我認為Array.map()是最直覺、合適的做法,完全不需要額外的狀態或暫存結構。

而這類一對一的轉換,實際上是在描述「資料如何被轉換」,而不是「資料之間的關係」。

問題出現:當資料不再是一對一

然而當需求改為「依照某個欄位進行分組」時,問題就開始出現了。

以資料employees為例,如果希望依照部門departmentId顯示員工清單,理想中的結果是:

[
  {
    departmentId: "d01",
    employees: [
      /* 2 位員工: Tom Smith、Chang lin */
    ],
  },
  {
    departmentId: "d02",
    employees: [
      /* 1 位員工:Chen Peiyu */
    ],
  },
];

很明顯可以看出,此時的資料長度已不同於原來的employees長度。

繼續使用原本的方法:object + for loop

const genderMap = {
  1: "male",
  2: "female",
};

const grouped = Object.create(null);

for (const emp of employees) {
  if (!grouped[emp.did]) {
    grouped[emp.did] = [];
  }
  grouped[emp.did].push({
    employeeId: emp.eid,
    departmentId: emp.did,
    fullname: emp.fullname,
    gender: genderMap[emp.gender] ?? "unknown",
  });
};
// { 
//    "d01": [/* 2 位員工: Tom Smith、Chang lin */], 
//    "d02": [/* 1 位員工:Chen Peiyu */]
// }

上面的grouped被用來暫存分組結果,其中 key 是departmentId,value 是該部門底下的employees

最後再做一次如下的轉換,便可以得到我所需要的資料結構。

const result = Object.entries(grouped).map(
  ([departmentId, employees]) => ({
    departmentId,
    employees,
  })
);

以上的轉換從結果上來,整個分組的過程是正確的。

然而這樣的寫法,問題不在於「結果是否正確」,而在於整個過程,有沒有清楚表達出「一個 key 對應多筆資料」的意圖。

在這裡,object 同時被用來暫時存放資料,並作為 key 與其對應資料之間的索引。 但這層語意並無法從grouped的宣告本身看出來,而是必須透過後續賦值與操作的方式,才能逐步推斷。

當資料或轉換過程在往後變得更複雜時,「一個 key 對應多筆資料」的意圖將會更難由這樣的過程得出。

意識並建立「索引」:改用Map

於是我改用 Map--本身就是設計用來專門描述 key 與其對應資料關係的索引結構。

const genderMap = {
  1: "male",
  2: "female"
};

const indexByDepartment = new Map();

for (const emp of employees) {
  if (!indexByDepartment.has(emp.did)) {
    indexByDepartment.set(emp.did, []);
  }

  indexByDepartment.get(emp.did).push({
    employeeId: emp.eid,
    departmentId: emp.did,
    fullname: emp.fullname,
    gender: genderMap[emp.gender] ?? "unknown"
  });
}

在這段 code 中,indexByDepartment的角色非常明確:

  • has:檢查某個 key 是否已存在
  • set:建立 key 與其對應資料的關係
  • get:透過 key 取得並累積對應的資料

這些操作直接說明「一個 key 對應多筆資料」的意圖。

實際上改用Map並沒有讓這段 code 的處理過程變得不同, 它和前一節的 object + for loop 在功能上是等價的; 真正的差異在於:索引的語意,從隱含在使用方式中,變成由資料結構本身所表達。

當看到Map時,已經能夠讓讀者預期接下來的實作過程是圍繞著「查找、建立對應關係、累積資料」這些行為展開,而不需要從操作細節中反推其用途。

也正因如此,在資料處理邏輯開始圍繞著「關係」而非「單筆轉換」時,Map會成為一個更貼近實際需求、也更容易被理解的選擇。

最後再轉成前端實際應用所需要的 array

當然使用Map建立完索引,做完資料的對應和處理後,為了讓前端可以應用,要再將資料轉換回 array:

const result = Array.from(
  indexByDepartment.entries(),
  ([departmentId, employees]) => ({
    departmentId,
    employees,
  })
);

最終可以得到結構如下的資料:

[
  {
    departmentId: "d01",
    employees: [
      {
        employeeId: "202242",
        departmentId: "d01",
        fullname: "Tom Smith",
        gender: "male",
      },
      {
        employeeId: "202505",
        departmentId: "d01",
        fullname: "Chang lin",
        gender: "female",
      },
    ],
  },
  {
    departmentId: "d02",
    employees: [
      {
        employeeId: "202414",
        departmentId: "d02",
        fullname: "Chen Peiyu",
        gender: "female",
      },
    ],
  },
];

結論就是,改用Map並不是為了改變最終資料格式,而是讓資料處理的過程,能更清楚地表達正在建立「一個 key 對應多筆資料」的關係。