✨ ES6 thiết yếu cho React: let, const, arrow function, destructuring, spread

By Better Wise

Đây là bài mở đầu của khóa. React được viết bằng JavaScript hiện đại (ES6+), nên gần như mọi dòng code React bạn gặp sau này đều dựa trên 5 thứ trong bài này: let/const, arrow function, template literal, destructuring và spread. Bạn đã biết HTML/CSS cơ bản rồi, nên ta chỉ cần thêm đúng phần JavaScript này là đủ để đọc và viết component.

Sau bài này, bạn sẽ:

  • Phân biệt let và const, biết chọn cái nào theo mặc định
  • Viết arrow function và tránh bẫy this cũng như bẫy trả về object
  • Dùng template literal thay cho nối chuỗi bằng dấu +
  • Bóc dữ liệu bằng destructuring object và array
  • Dùng spread để tạo bản sao thay vì sửa trực tiếp dữ liệu gốc

let và const: mặc định là const, chỉ đổi khi thật cần

Hãy nghĩ tới hai chiếc hộp dán nhãn. const là hộp dán băng keo: nhãn không đổi được. let là hộp dán nhãn bằng keo giấy: đổi nhãn lúc nào cũng được. Còn var là chiếc hộp cũ rò rỉ ra ngoài mọi ngăn kéo, ta bỏ hẳn.

const tenApp = "Quản lý chi tiêu";
// tenApp = "Khác"; // TypeError: Assignment to constant variable

let soDu = 0;
soDu = 500000; // OK

Điểm quan trọng nhất, và cũng là chỗ người mới hiểu sai nhiều nhất:

const user = { ten: "Lan", tuoi: 20 };
user.tuoi = 21;      // OK! const không đóng băng nội dung object
// user = {};        // Lỗi: gán lại biến thì không được
Khai báo Gán lại biến Phạm vi Dùng khi nào
var được hàm tránh trong code mới
let được block biến đếm, biến cộng dồn, giá trị thật sự thay đổi
const không block mặc định cho hầu hết khai báo
flowchart TD
    Start["Cần khai báo biến"] --> Q{"Giá trị có bị gán lại?"}
    Q -->|"Không"| Const["📌<br/>const"]
    Q -->|"Có"| Let["🔄<br/>let"]

Bài tập 1. Trong dự án quản lý chi tiêu, bạn cần một biến cộng dồn tổng tiền khi duyệt mảng các khoản chi. Dùng let hay const? Vì sao?

Xem gợi ý

Dùng let tong = 0; rồi tong += khoan.tien; trong vòng lặp, vì biến này bị gán lại nhiều lần. Mảng khoanChi thì dùng const.

Arrow function: ngắn hơn, và this không bị đổi

// Cách cũ
const chaoCu = function (ten) {
  return "Xin chào " + ten;
};

// Arrow function
const chao = (ten) => `Xin chào ${ten}`;

Ba quy tắc viết gọn:

  • Một tham số thì bỏ được ngoặc: x => x * 2
  • Thân hàm chỉ có một biểu thức thì bỏ được return và {}
  • Trả về object literal thì phải bọc ngoặc tròn: () => ({ id: 1 })

Điều thứ ba là bẫy kinh điển. Không có ngoặc tròn, JavaScript hiểu {} là thân hàm, nên hàm trả về undefined và bạn ngồi tìm lỗi cả buổi.

Về this: arrow function không có this riêng, nó dùng this của phạm vi bao ngoài. Với function component hiện đại bạn ít gặp chuyện này, nhưng đó là lý do các callback trong React luôn được viết bằng arrow function.

Bài tập 2. Viết lại hàm sau thành arrow function một dòng: function nhan2(x) { return x * 2; }

Xem đáp án

const nhan2 = (x) => x * 2;

Template literal: ghép chuỗi bằng backtick

Dấu backtick ` cho phép chèn biểu thức bằng ${} và viết chuỗi nhiều dòng mà không cần \n. Trong React, bạn dùng nó nhiều nhất ở hai chỗ: className động và URL API.

const dangChon = true;
const lop = `menu-item ${dangChon ? "active" : ""}`;

const API_BASE = "https://api.example.com";
const url = `${API_BASE}/todos/${id}`;

Bài tập 3. Từ hai biến ten = "Lan" và soViec = 3, tạo chuỗi Xin chào Lan, bạn có 3 việc cần làm.

Xem đáp án

const loi = `Xin chào ${ten}, bạn có ${soViec} việc cần làm`;

Destructuring: bóc dữ liệu ra khỏi object và array

const sanPham = { id: 1, ten: "Bàn phím", gia: 500000 };

const { ten, gia } = sanPham;              // lấy đúng tên field
const { ten: tenSP, gia: giaSP } = sanPham; // đổi tên biến
const { ten = "Chưa có tên" } = sanPham;    // giá trị mặc định khi field thiếu

const diem = [9, 8, 7];
const [dau, hai] = diem;   // dau = 9, hai = 8
const [, , ba] = diem;     // bỏ qua phần tử đầu: ba = 7

Đây chính là lý do bài này quan trọng với React. Hai dòng code bạn sẽ gặp ở mọi component:

const [count, setCount] = useState(0); // array destructuring

function TheSanPham({ ten, gia }) {    // destructuring ngay trong tham số
  return <div>{ten} - {gia}</div>;
}

Bài tập 4. Cho const nguoiDung = { hoTen: "Minh", email: "[email protected]" }. Viết một dòng destructuring lấy hoTen vào biến tên ten.

Xem đáp án

const { hoTen: ten } = nguoiDung;

Spread và rest: ... làm hai việc khác nhau

Spread trải giá trị ra, rest gom giá trị lại. Cùng một ký hiệu, khác ngữ cảnh.

// Spread: tạo bản sao
const cu = [1, 2, 3];
const moi = [...cu, 4];                    // [1, 2, 3, 4]

const user = { ten: "Lan", tuoi: 20 };
const capNhat = { ...user, tuoi: 21 };     // ghi đè đúng một field

// Rest: gom phần còn lại
const { ten, ...phanConLai } = user;
function tong(...cacSo) {
  return cacSo.reduce((a, b) => a + b, 0);
}

Trong React, spread là cách chuẩn để cập nhật state mà không sửa thẳng dữ liệu cũ. Mặc dù bài này chưa chạm tới useState, hãy nhớ sẵn hình mẫu setTodos([...todos, viecMoi]) và setUser({ ...user, ten: "Minh" }).

Lưu ý: spread chỉ là shallow copy. Object lồng bên trong vẫn dùng chung tham chiếu với object gốc.

Bài tập 5. Cho const hoaDon = { ma: "HD01", tong: 100, daThanhToan: false }. Viết code tạo object mới đã thanh toán mà không sửa hoaDon.

Xem đáp án

const daThanhToan = { ...hoaDon, daThanhToan: true };

Điền khuyết: kiểm tra nhanh cú pháp ES6

  1. Từ ES6, ta nên dùng _____ làm mặc định khi khai báo biến, chỉ dùng let khi biến bị gán lại.
  2. Arrow function không có _____ riêng, nó dùng của phạm vi bao ngoài.
  3. const [count, setCount] = useState(0) là ví dụ của _____.
  4. Để tạo bản sao object rồi ghi đè một field, ta viết { ...user, tuoi: 21 }, trong đó ... là _____ operator.
  5. Muốn nối giá trị biến vào chuỗi từ ES6, ta dùng _____ với dấu backtick.
  6. const { ten: hoTen } = nguoi lấy field ten nhưng _____ biến thành hoTen.

Thẻ ghi nhớ ES6

Front: const khác let ở điểm nào? Back: const không cho gán lại biến, let thì cho phép.

Front: Vì sao nên mặc định dùng const? Back: Vì nó báo lỗi ngay khi vô tình gán lại, giúp code an toàn hơn.

Front: const khai báo object thì có sửa được thuộc tính không? Back: Có, const chỉ chặn gán lại biến chứ không đóng băng nội dung object.

Front: Arrow function khác function thường ở điểm cốt lõi nào? Back: Arrow function không có this riêng, nó dùng this của phạm vi bao ngoài.

Front: Arrow function trả về object literal phải viết thế nào? Back: Bọc object trong ngoặc tròn: () => ({ id: 1 }).

Front: Khi nào bỏ được ngoặc đơn tham số của arrow function? Back: Khi chỉ có đúng một tham số, ví dụ x => x * 2.

Front: Template literal dùng cặp ký tự gì để bao chuỗi? Back: Dấu backtick, và chèn biểu thức bằng ${...}.

Front: Object destructuring lấy field ten và đổi tên thành hoTen viết thế nào? Back: const { ten: hoTen } = nguoi;

Front: Destructuring có hỗ trợ giá trị mặc định không? Back: Có: const { ten = 'Khách' } = nguoi;

Front: const [count, setCount] = useState(0) dùng kỹ thuật gì? Back: Array destructuring.

Front: Spread operator làm gì với object? Back: Tạo bản sao nông và có thể ghi đè field: { ...user, ten: 'Minh' }.

Front: Vì sao React cần spread khi cập nhật state? Back: Vì không được mutate state trực tiếp, phải tạo object hoặc array mới.

Front: Rest khác spread ở đâu? Back: Rest gom nhiều giá trị còn lại vào một biến, spread trải giá trị ra.

Front: Spread có copy sâu object lồng nhau không? Back: Không, đó là shallow copy, object con vẫn chia sẻ tham chiếu.

Bốn lỗi ES6 hay gặp khi bắt đầu với React

  • Sửa thẳng object thay vì tạo bản sao. user.ten = "Minh" chạy được với const, nhưng React sẽ không thấy dữ liệu đổi. Luôn dùng { ...user, ten: "Minh" }.
  • Quên ngoặc tròn khi arrow function trả về object. () => { id: 1 } trả về undefined, phải là () => ({ id: 1 }).
  • Dùng let cho mọi thứ. Thói quen này khiến biến bị gán lại mà không ai báo lỗi. Mặc định const, chỉ nâng lên let khi cần.
  • Quên rằng destructuring không tạo bản sao. const { ten } = user vẫn trỏ tới cùng chuỗi, còn const { diaChi } = user thì diaChi vẫn là object gốc của user, sửa nó là sửa luôn user.

Mẹo nhanh: nếu bạn thấy dấu + đang nối chuỗi, gần như luôn có một template literal gọn hơn ở đó.

Mang theo gì khi sang bài map, filter, reduce

Năm điều cần nhớ: const là mặc định, let chỉ khi biến đổi giá trị; arrow function ngắn gọn nhưng phải bọc ngoặc tròn khi trả về object; template literal thay cho nối chuỗi; destructuring lấy đúng field bạn cần; spread tạo bản sao thay vì sửa dữ liệu gốc.

Bài tiếp theo, Hàm và mảng trong thực tế: map, filter, reduce, sẽ dùng chính arrow function và destructuring bạn vừa học để biến một mảng dữ liệu thô thành danh sách hiển thị được trên giao diện.