# 🔁 Hàm và mảng trong thực tế: map, filter, reduce

By Better Wise

> Sign in to view the full content of this lesson.

Bài trước bạn đã có arrow function, destructuring và spread, đó chính là nguyên liệu. Bài này ghép chúng lại thành ba hàm mà bạn sẽ dùng gần như mỗi ngày khi viết React: `map`, `filter`, `reduce`.

Sau khoảng 30 phút, bạn sẽ:

- Giải thích được vì sao React ưu tiên biến đổi mảng thay vì tự thao tác DOM
- Dùng `map` để biến đổi từng phần tử và giữ nguyên số lượng
- Dùng `filter` để lọc theo điều kiện
- Dùng `reduce` để gộp cả mảng về một giá trị
- Ghép chuỗi ba hàm trên và biết khi nào nên dùng hàm khác

## Vì sao React không duyệt mảng bằng vòng lặp DOM

Bạn đã quen đoạn HTML này: một thẻ `<ul>` rỗng, rồi JavaScript tự tạo từng `<li>` và gắn vào.

```html
<ul id="ds"></ul>
<script>
  const ds = document.getElementById("ds");
  for (const cv of danhSach) {
    const li = document.createElement("li");
    li.textContent = cv.title;
    ds.appendChild(li);
  }
</script>
```

Cách này chạy được, nhưng có một cái bẫy: dữ liệu và giao diện là hai nơi phải tự tay giữ đồng bộ. Người dùng bấm "xoá", bạn phải tự tìm đúng `<li>` để xoá, tự cập nhật số đếm, tự đổi màu khi trạng thái đổi. Quên một chỗ là giao diện lệch với dữ liệu.

React đổi hướng suy nghĩ: dữ liệu là nguồn sự thật duy nhất, giao diện chỉ là kết quả của dữ liệu. Bạn không nói "hãy tạo thẻ `li` này", bạn nói "với mỗi công việc trong mảng, hãy hiển thị một dòng". Cách mô tả đó chính là `map`.

Kèm theo một luật ngầm quan trọng: **không sửa mảng gốc**, luôn tạo _____. `push`, `splice`, `sort` làm thay đổi mảng gốc; `[...arr, item]` (spread bạn vừa học) thì tạo mảng mới. Lý do sâu xa sẽ rõ ở module 2, giờ chỉ cần nhớ luật.

```mermaid
flowchart TD
  A["📦 Mảng dữ liệu"] --> B["🔍 filter"]
  B --> C["🔄 map"]
  C --> D["🖥️ Giao diện"]
  A --> E["➕ reduce"]
  E --> F["🔢 Một con số / object"]
```

Dùng chung mảng này cho cả bài, giống dữ liệu thật trong ứng dụng quản lý công việc:

```js
const congViec = [
  { id: 1, title: "Học map", done: true, gio: 2 },
  { id: 2, title: "Làm bài tập", done: false, gio: 3 },
  { id: 3, title: "Ôn filter", done: false, gio: 1 },
];
```

## map: biến đổi từng phần tử, giữ nguyên số lượng

`map` chạy callback trên từng phần tử và luôn trả về _____ với mảng gốc.

```js
const nhan = congViec.map((cv) => cv.title);
// ["Học map", "Làm bài tập", "Ôn filter"]

const nhanKemGio = congViec.map((cv) => `${cv.title} (${cv.gio}h)`);
// ["Học map (2h)", "Làm bài tập (3h)", "Ôn filter (1h)"]
```

Callback nhận ba tham số, hai tham số sau hay dùng khi cần vị trí:

```js
congViec.map((cv, index) => `${index + 1}. ${cv.title}`);
```

Lỗi người mới hay mắc nhất: viết `map` với dấu ngoặc nhọn mà quên `return`.

```js
congViec.map((cv) => { cv.title });      // [undefined, undefined, undefined]
congViec.map((cv) => { return cv.title; }); // đúng
```

> **Warning:** 
> `map` không dùng để "làm gì đó" như in log hay gọi API. Nếu bạn không cần mảng kết quả, hãy dùng `forEach`. Dùng `map` chỉ vì tiện là dấu hiệu code đang gây hiểu nhầm cho người đọc sau.

**Bài tập 1.** Từ `congViec`, tạo mảng `trangThai` chứa chuỗi `"xong"` hoặc `"chưa xong"`.

**Xem gợi ý**

```js
const trangThai = congViec.map((cv) => (cv.done ? "xong" : "chưa xong"));
// ["xong", "chưa xong", "chưa xong"]
```

Dùng toán tử ba ngôi bên trong arrow function rút gọn được vì không cần `return`.

## filter: giữ lại phần tử thoả điều kiện

`filter` trả về mảng mới chỉ gồm những phần tử mà callback trả về _____. Độ dài mảng kết quả có thể nhỏ hơn, kể cả bằng 0.

```js
const chuaXong = congViec.filter((cv) => !cv.done);
// 2 phần tử: "Làm bài tập", "Ôn filter"

const viecNang = congViec.filter((cv) => cv.gio > 1);
// 2 phần tử
```

Vì callback chỉ cần truthy/falsy, bạn có thể viết gọn `congViec.filter((cv) => cv.done)`. Nhưng cẩn thận với số: `filter((cv) => cv.gio)` sẽ loại luôn công việc có `gio: 0`. Khi so sánh, hãy viết rõ điều kiện.

**Bài tập 2.** Lấy các công việc **chưa xong** và tốn **hơn 1 giờ**.

**Xem gợi ý**

```js
const ketQua = congViec.filter((cv) => !cv.done && cv.gio > 1);
// chỉ còn "Làm bài tập" (3h)
```

Hai điều kiện nối bằng `&&`, đọc theo đúng thứ tự bạn sẽ nói bằng lời.

## reduce: gộp cả mảng thành một giá trị

`reduce` nhận hai tham số: hàm xử lý `(acc, item)` và _____. `acc` là "cái hộp" được truyền qua từng vòng, mỗi vòng bạn trả về hộp mới.

```js
const tongGio = congViec.reduce((acc, cv) => acc + cv.gio, 0);
// 0 + 2 = 2 -> 5 -> 6
```

Hình dung bằng đời thường: bạn đi chợ với một cái ví có 0 đồng, mỗi món hàng bạn cộng thêm tiền vào ví. `acc` là cái ví, `0` là số tiền ban đầu.

Luôn truyền initial value. Bỏ đi thì `acc` lấy phần tử đầu tiên và kiểu dữ liệu trở nên khó đoán, mảng rỗng thì báo lỗi ngay.

`reduce` không chỉ để tính tổng. Nó gộp được về object, ví dụ đếm số việc theo trạng thái:

```js
const thongKe = congViec.reduce(
  (acc, cv) => {
    acc[cv.done ? "xong" : "chuaXong"] += 1;
    return acc;
  },
  { xong: 0, chuaXong: 0 }
);
// { xong: 1, chuaXong: 2 }
```

**Bài tập 3.** Tính tổng số giờ của các công việc **chưa xong**.

**Xem gợi ý**

```js
const tongGioChuaXong = congViec
  .filter((cv) => !cv.done)
  .reduce((acc, cv) => acc + cv.gio, 0);
// 4
```

Cách khác, gộp trong một lần `reduce`: `acc + (cv.done ? 0 : cv.gio)`. Cách đầu dễ đọc hơn, và đó thường là lựa chọn đúng.

## Ghép chuỗi: filter trước, map sau

Ba hàm đều trả về mảng mới nên ghép nối tiếp được. Thứ tự có ý nghĩa: `filter` trước thì `map` chỉ chạy trên phần tử đã lọc, ít việc hơn và ngữ nghĩa rõ hơn.

```js
const nhanViecNang = congViec
  .filter((cv) => cv.gio > 1)
  .map((cv) => cv.title.toUpperCase());
// ["HỌC MAP", "LÀM BÀI TẬP"]
```

Đến module 2, bạn sẽ thấy đúng chuỗi này nhưng `map` trả về JSX thay vì chuỗi, và React lo phần render. Cấu trúc suy nghĩ thì y hệt hôm nay.

Vài trường hợp **không** nên dùng ba hàm này:

| Nhu cầu | Dùng |
| --- | --- |
| Chỉ cần biết có phần tử thoả điều kiện | `some` |
| Cần tìm một phần tử cụ thể | `find` |
| Cần dừng giữa chừng | `for...of` |
| Chỉ cần chạy side effect | `forEach` |

## Bẫy thường gặp khi dùng map, filter, reduce

- **Quên `return` trong arrow function có `{}`.** Đây là lỗi số một, kết quả là mảng `undefined` và bạn sẽ ngồi nhìn màn hình rất lâu.
- **Quên initial value cho `reduce`.** Luôn truyền `0` hoặc `{}` hoặc `[]` đúng kiểu bạn muốn nhận về.
- **Sửa mảng gốc bằng `push`/`sort`.** Hãy tạo bản sao trước: `[...arr].sort(...)`.
- **Dùng `reduce` cho mọi thứ.** Nếu bạn phải đọc lại ba lần mới hiểu, hãy tách thành `filter` rồi `reduce`, hoặc viết hàm phụ có tên rõ ràng.
- **Đặt tên biến vô nghĩa.** `arr2`, `data1` sẽ giết khả năng đọc code. Hãy đặt `viecChuaXong`, `tongGio`.
- **Mẹo debug:** `console.log` sau từng bước của chuỗi, đừng log cả chuỗi dài một lần.

## Tóm lại và bước tiếp theo

Ba hàm, ba câu hỏi khác nhau bạn hỏi mảng dữ liệu:

- `map`: "biến đổi **từng phần tử** thành gì?" (giữ nguyên số lượng)
- `filter`: "giữ lại phần tử nào?" (có thể ít hơn)
- `reduce`: "gộp tất cả thành **một giá trị** gì?"

Cả ba đều không thay đổi mảng gốc (_____), trả về mảng mới, và ghép chuỗi được. Đó chính là lý do React có thể dựa hoàn toàn vào dữ liệu để quyết định giao diện: bạn mô tả, React tự tính phần DOM cần sửa.

Bài tiếp theo, bạn sẽ dùng chính tư duy này để chia giao diện thành các **component** và khởi tạo dự án React đầu tiên bằng Vite.

Front: map trả về gì?
Back: Mảng mới cùng độ dài, mỗi phần tử là kết quả của callback.

Front: filter trả về gì?
Back: Mảng mới chỉ gồm các phần tử mà callback trả về truthy.

Front: reduce dùng để làm gì?
Back: Gộp cả mảng về một giá trị duy nhất: số, chuỗi, object hoặc mảng.

Front: Tham số thứ hai của reduce là gì?
Back: Initial value, tức giá trị khởi tạo của acc.

Front: Vì sao nên luôn truyền initial value cho reduce?
Back: Tránh lỗi với mảng rỗng và tránh kiểu dữ liệu của acc bị đoán sai.

Front: map, filter, reduce có thay đổi mảng gốc không?
Back: Không. Chúng trả về mảng mới, giữ nguyên mảng gốc.

Front: Vì sao React dùng map thay vì vòng lặp tạo DOM thủ công?
Back: Vì dữ liệu là nguồn sự thật duy nhất, React tự cập nhật DOM khi dữ liệu đổi.

Front: Lỗi gì xảy ra với `arr.map(x => { x * 2 })`?
Back: Thiếu return nên kết quả là mảng gồm toàn undefined.

Front: Muốn kiểm tra có phần tử thoả điều kiện mà không cần mảng mới, dùng gì?
Back: Dùng some (hoặc find nếu cần chính phần tử đó).

Front: Tham số thứ hai của callback trong map là gì?
Back: index, vị trí của phần tử trong mảng.

Front: Vì sao nên filter trước rồi mới map?
Back: map chỉ chạy trên phần tử đã lọc, ít việc hơn và ngữ nghĩa rõ hơn.

Front: Câu hỏi mà filter trả lời là gì?
Back: Giữ lại phần tử nào trong mảng.

Front: Khi nào không nên dùng map?
Back: Khi chỉ cần chạy side effect, không cần mảng kết quả. Dùng forEach.

---

## Contents

Part of [⚛️ React từ con số 0 cho người chỉ biết HTML](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html.md)

- **🧱 Nền tảng JavaScript và tư duy React**
  - [✨ ES6 thiết yếu cho React: let, const, arrow function, destructuring, spread](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/es6-thiet-yeu-cho-react.md)
  - [🔁 Hàm và mảng trong thực tế: map, filter, reduce](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/ham-mang-map-filter-reduce.md) (sign-in required) — this lesson
  - [🧩 Tư duy component và khởi tạo dự án với Vite](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/tu-duy-component-va-khoi-tao-vite.md)
  - [✅ Checkpoint: nền tảng JavaScript và tư duy React](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-nen-tang-javascript-va-react.md)
- **🧩 Component, JSX, props và state**
  - [📄 JSX và component đầu tiên](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/jsx-va-component-dau-tien.md)
  - [📦 Props và composition: truyền dữ liệu giữa component](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/props-va-composition.md)
  - [🔄 State với useState và xử lý sự kiện](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/state-usestate-va-su-kien.md)
  - [📋 Render danh sách và key trong React](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/render-danh-sach-va-key.md)
  - [✅ Checkpoint: component, JSX, props và state](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-component-va-state.md)
- **🚀 Form, useEffect, gọi API và dự án nhỏ**
  - [📝 Form và controlled components](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/form-va-controlled-components.md)
  - [⚙️ useEffect và side effects](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/useeffect-va-side-effects.md)
  - [🌐 Gọi API với fetch: loading, error và cleanup](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/goi-api-voi-fetch.md) (premium content)
  - [🛠️ Dự án: ứng dụng quản lý nhỏ với Vite](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/du-an-ung-dung-quan-ly-voi-vite.md) (premium content)
  - [✅ Checkpoint: form, useEffect, gọi API và dự án](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/checkpoint-form-api-va-du-an.md) (premium content)
- [Course cheatsheet](https://everwise.study/courses/react-tu-con-so-0-cho-nguoi-chi-biet-html/cheatsheet.md) (premium content)
