Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

Tất Cả Câu Hỏi

DOCTYPE (Document Type Declaration) khai báo phiên bản HTML đang sử dụng, đặt ở dòng đầu tiên.

  • HTML5 chỉ cần <!DOCTYPE html>.
  • Giúp trình duyệt render đúng chuẩn (standards mode).
  • Không có DOCTYPE → quirks mode: box model, CSS parsing khác nhau giữa các browsers, gây lỗi hiển thị.

Cấu trúc cơ bản của tài liệu HTML bao gồm các phần chính sau:

  • <!DOCTYPE html>: Khai báo loại tài liệu cho trình duyệt
  • <html>: Phần tử gốc (root element) bọc toàn bộ trang
  • <head>: Chứa siêu dữ liệu (metadata) như <title>, <meta>, <link>, <style>
  • <body>: Chứa toàn bộ nội dung sẽ hiển thị trên trang web.

Block element chiếm toàn bộ chiều rộng, bắt đầu dòng mới (<div>, <p>, <h1>).

  • Inline element chỉ chiếm chiều rộng cần thiết (<span>, <a>, <strong>).
  • Block chứa được inline, nhưng thường không ngược lại.
  • Ngoại lệ HTML5: <a> có thể bọc block elements.

Attribute cung cấp thông tin bổ sung cho HTML element, viết trong thẻ mở theo cú pháp name="value".

Ví dụ: <a href="https://example.com" target="_blank">Link</a>. Các attribute phổ biến: href, src, class, id, style, alt, type, placeholder. Một số attribute không cần giá trị (boolean): disabled, checked, required.

Lưu ý: attribute name không phân biệt hoa thường nhưng giá trị thì có.

Boolean attribute là loại attribute trong HTML chỉ có hai trạng thái: có mặt trong thẻ nghĩa là true, vắng mặt nghĩa là false — giá trị gán cho nó không quan trọng. Ví dụ, viết <input disabled> hoặc <input disabled=""> hoặc <input disabled="disabled"> đều cho kết quả giống nhau là input bị vô hiệu hóa.

Các boolean attribute phổ biến bao gồm disabled, checked, selected, required, readonly, autofocus, hidden, multiple. Một lỗi rất hay gặp là viết disabled="false" với ý định bật lại — nhưng thực tế element vẫn bị disable vì browser chỉ kiểm tra attribute có tồn tại hay không, không quan tâm giá trị.

Cho phép lưu trữ dữ liệu tùy chỉnh trên HTML element mà không vi phạm HTML spec.

  • Cú pháp: data-user-id="123".
  • Truy cập qua JS bằng element.dataset.userId (tự convert kebab-case sang camelCase). Ứng dụng: truyền data từ HTML sang JS, CSS selectors ([data-active]), testing (data-testid).
  • React dùng data attributes để pass info cho DOM.

Semantic HTML dùng thẻ có ý nghĩa mô tả nội dung (<article>, <nav>, <header>, <main>, <aside>) thay vì thẻ chung (<div>, <span>).

Quan trọng vì:

  1. SEO — search engines hiểu cấu trúc trang tốt hơn.
  2. Accessibility — screen readers navigate bằng semantic landmarks.
  3. Maintainability — code tự mô tả, đọc hiểu nhanh hơn.
  4. Consistency — team dùng cùng conventions

<div> là container chung không có ý nghĩa ngữ nghĩa — dùng khi chỉ cần nhóm để style hoặc layout. <section> đại diện cho phần nội dung có chủ đề liên quan, thường có <h2> hoặc heading đi kèm.

  • Screen readers nhận diện <section> là landmark region.

Ví dụ thực tế: một trang product dùng <section> cho phần mô tả, reviews, specs — không dùng <div> cho các khối nội dung có ý nghĩa riêng biệt.

<article> là nội dung độc lập, có thể đứng riêng và vẫn có nghĩa (bài blog, comment, card sản phẩm, tweet). <section> là nhóm nội dung liên quan trong cùng tài liệu, cần context của trang.

  • Test nhanh: nếu có thể syndicate nội dung đó ra site khác mà vẫn hiểu được → dùng <article>.
  • Article thường có <header>, <footer>, byline riêng.
  • Article có thể chứa nhiều <section><section> có thể chứa nhiều <article>.

<header>: phần giới thiệu/navigation của trang hoặc section (có thể dùng nhiều lần). <footer>: thông tin cuối trang (copyright, liên hệ, links). <nav>: navigation links chính — screen readers cho phép nhảy thẳng đến đây. <main>: nội dung chính, duy nhất 1 per page, screen readers có thể nhảy thẳng đến <main> (skip navigation). <aside>: nội dung liên quan gián tiếp (sidebar, related posts, ads).

Lưu ý: <header><footer> không nhất thiết chỉ dùng ở top/bottom trang — mỗi <article> cũng có thể có header riêng.

<figure> nhóm nội dung minh họa có thể tách ra mà không ảnh hưởng luồng văn bản chính: ảnh, biểu đồ, code snippet, video. <figcaption> là chú thích mô tả, đặt đầu hoặc cuối <figure>.

Ví dụ: <figure><img src="chart.png" alt="Biểu đồ doanh thu Q1 2024"><figcaption>Biểu đồ doanh thu Q1</figcaption></figure>. <figcaption> bổ sung thêm cho alt, không thay thế nó — alt vẫn cần mô tả nội dung ảnh cho screen readers; figcaption là caption hiển thị cho tất cả users. Nếu figcaption đã mô tả đầy đủ, alt có thể để rỗng (alt=""). Khác với chỉ dùng <img>: figure semantically đóng gói ảnh kèm caption.

HTML5 mang đến nhiều cải tiến lớn so với HTML4 trên 3 nhóm chính:

Semantic & Media: thẻ semantic (<header>, <nav>, <article>, <section>), <audio>/<video> native không cần Flash, Canvas/SVG cho đồ họa.

Storage & APIs: Web Storage (localStorage, sessionStorage), IndexedDB cho dữ liệu lớn, Geolocation API, Web Workers, WebSocket, Drag & Drop.

Forms: form validation tích hợp, các input types mới (email, date, number, range, color).

Canvas là pixel-based (raster), vẽ bằng JavaScript, tốt cho game/animation phức tạp.

  • SVG là vector-based (XML), scale không mất chất lượng, tốt cho icon/chart/logo.
  • SVG accessible hơn vì là DOM elements.

GET gửi dữ liệu qua URL (query string), giới hạn kích thước, có thể bookmark. POST gửi trong body request, không giới hạn kích thước.

Lưu ý: POST không tự bảo mật hơn GET — cần HTTPS để bảo vệ dữ liệu. GET cho truy vấn, POST cho dữ liệu lớn/thay đổi state.

HTML5 cung cấp nhiều input types với validation và UI riêng theo nhóm:

Text/Data: text, password, email, number, tel, url, search.

Date/Time: date, time, datetime-local, month, week.

Interactive/Media: color, range, file, checkbox, radio, hidden, submit, reset, button.

<label> liên kết mô tả với form control theo 2 cách: dùng for khớp với id của input (<label for="email">Email</label><input id="email">), hoặc wrapping trực tiếp.

  • Click vào label = focus/check input — tăng click area đáng kể trên mobile.
  • Screen readers đọc label khi user focus vào input.
  • Lỗi phổ biến: chỉ dùng placeholder thay label — placeholder biến mất khi gõ, không accessible.
  • Placeholder nên là gợi ý, không phải label.

<select> là dropdown cho phép chọn từ danh sách cố định — user không thể nhập giá trị ngoài list. <datalist> gắn vào <input> qua list attribute, cung cấp gợi ý tự động điền nhưng user vẫn gõ giá trị tùy ý.

Ví dụ datalist: ô tìm kiếm gợi ý nhưng cho phép search từ khóa mới.

<fieldset> nhóm các form controls có liên quan nhau, tạo border visual và semantic grouping. <legend> là tiêu đề mô tả cho nhóm đó.

  • Đặc biệt quan trọng cho nhóm radio/checkbox: screen readers đọc legend trước mỗi option (ví dụ: 'Giới tính: Nam / Nữ').
  • Có thể dùng disabled trên <fieldset> để disable toàn bộ group inputs bên trong. Ứng dụng: form đăng ký nhiều section (Thông tin cá nhân, Địa chỉ, Thanh toán).

Accessibility (a11y — viết tắt 11 chữ cái giữa) là khả năng web được dùng bởi mọi người, kể cả người khuyết tật (thị giác, thính giác, vận động, nhận thức).

Quan trọng vì:

  1. Đạo đức — ~15% dân số thế giới có dạng khuyết tật nào đó.
  2. Luật pháp — ADA (Mỹ), WCAG 2.1 AA là yêu cầu pháp lý ở nhiều nơi.
  3. Business — site accessible rank SEO tốt hơn, reach rộng hơn.
  4. UX — closed captions giúp cả người xem không tiếng ồn; good contrast giúp mọi người dưới nắng

ARIA (Accessible Rich Internet Applications) là tập hợp attributes bổ sung accessibility cho HTML khi semantic không đủ. 3 loại:

  1. Roles — xác định vai trò (role="button", role="dialog").
  2. States — trạng thái (aria-expanded="true", aria-disabled).
  3. Properties — thuộc tính (aria-label, aria-describedby)

Nguyên tắc: dùng native HTML trước, ARIA là giải pháp cuối cùng.

Alt text mô tả hình ảnh cho screen readers và hiện khi ảnh không load được. 3 trường hợp: ảnh mang thông tin — mô tả ngắn gọn nội dung/mục đích (không bắt đầu bằng 'Hình ảnh của...'); ảnh là link — mô tả đích đến; ảnh decorative thuần túy — alt="" để screen reader bỏ qua.

  • Quan trọng cho SEO: Google Images index dựa vào alt text.
  • Lỗi phổ biến: alt="image123.jpg" (filename), alt="logo" quá chung chung, hoặc bỏ qua alt hoàn toàn.

Cơ chế quyết định style nào được áp dụng khi có xung đột.

  • Thứ tự ưu tiên đầy đủ (từ thấp đến cao): @layer order < specificity < source order < origin (browser default < user styles < author styles) < !important < inline styles.
  • CSS 2021 thêm @layer để quản lý nhóm styles — styles trong layer sau override layer trước bất kể specificity, và styles ngoài @layer có ưu tiên cao hơn mọi layer.
  • Cascade giúp quản lý styles phức tạp.

Specificity tính theo 4 cột (inline, ID, class, element): inline styles (1,0,0,0) > ID (0,1,0,0) > class/pseudo-class/attribute (0,0,1,0) > element/pseudo-element (0,0,0,1).

So sánh từ trái sang phải, cột nào lớn hơn thắng. !important override tất cả (tránh dùng).

Một số CSS properties tự động kế thừa từ parent sang child (color, font-family, font-size, line-height).

  • Các properties về box model (margin, padding, border) KHÔNG kế thừa.
  • Dùng inherit, initial, unset để override.

px: tuyệt đối. em: relative với font-size parent. rem: relative với root (html) font-size. %: relative với parent. vw/vh: % viewport width/height. rem được khuyến nghị cho responsive design. 2023+: svh/dvh/lvh (small/dynamic/large viewport height) xử lý địa chỉ bar mobile tốt hơn vh truyền thống; cqi/cqb cho container query units.

  • Override tất cả specificity rules.
  • Nên tránh vì: khó debug, khó maintain, phá vỡ cascade tự nhiên.
  • Chỉ dùng cho utility classes hoặc override third-party styles khi không có cách khác.

Pseudo-class chọn state của element (:hover, :focus, :first-child, :nth-child).

  • Pseudo-element tạo/chọn phần ảo (::before, ::after, ::first-line).
  • Pseudo-class dùng 1 dấu hai chấm, pseudo-element dùng 2.

4 lớp từ trong ra: content (nội dung), padding (khoảng đệm), border (viền), margin (khoảng cách ngoài).

  • Mặc định width/height chỉ tính content.
  • Dùng box-sizing: border-box để tính luôn padding + border.

content-box (mặc định): width/height chỉ tính content, padding+border cộng thêm. border-box: width/height bao gồm content+padding+border. border-box dễ quản lý hơn, nên dùng * { box-sizing: border-box }.

display: none: xóa khỏi layout, không chiếm không gian. visibility: hidden: ẩn nhưng vẫn chiếm không gian. opacity: 0: trong suốt, vẫn chiếm không gian VÀ nhận events. display: none truyền thống không animatable — tuy nhiên CSS @starting-style (Chrome 117+, Firefox 129+) cho phép animate từ display: none bằng transition.

static (mặc định, theo flow). relative (offset từ vị trí gốc, vẫn chiếm space). absolute (thoát flow, relative với positioned ancestor). fixed (relative với viewport). sticky (hybrid relative+fixed).

Layout 1 chiều (row hoặc column) cho phép căn chỉnh, phân bổ không gian linh hoạt giữa items.

  • Dùng cho: navigation bars, card layouts, centering, form layouts.
  • Grid phù hợp hơn cho layout 2 chiều.

justify-content căn chỉnh theo main axis (mặc định horizontal): flex-start, center, flex-end, space-between, space-around, space-evenly. align-items căn theo cross axis (mặc định vertical): stretch, center, flex-start, flex-end, baseline.

Layout 2 chiều (rows VÀ columns cùng lúc).

  • Flexbox cho 1 chiều (row hoặc column).
  • Grid tốt cho page layout tổng thể, Flexbox cho component layout.
  • Có thể kết hợp cả hai.

Fractional unit - đơn vị tỷ lệ trong Grid. grid-template-columns: 1fr 2fr = cột 2 gấp đôi cột 1.

  • Fr phân bổ không gian còn lại sau khi trừ fixed sizes.
  • Linh hoạt hơn % vì tự tính gap.

Thiết kế web tự điều chỉnh giao diện theo kích thước màn hình (desktop, tablet, mobile).

  • Dùng fluid layouts (%, fr), flexible images, media queries.
  • Mobile-first approach được khuyến nghị.

Viết CSS cho mobile trước (base styles), dùng min-width media queries thêm styles cho màn hình lớn hơn.

Ưu điểm: performance tốt hơn trên mobile, progressive enhancement, code gọn hơn.

@media (min-width: 768px) { ... } áp dụng styles khi viewport >= 768px.

  • Có thể kết hợp: @media (min-width: 768px) and (max-width: 1024px).
  • Breakpoints phổ biến: 640, 768, 1024, 1280px.

Biến trong CSS: --primary: #2563eb.

  • Truy cập: var(--primary).
  • Khai báo trong :root cho global.
  • Có cascade/inheritance, thay đổi runtime bằng JS.
  • Hỗ trợ fallback: var(--color, blue).

Naming convention CSS: block__element--modifier. Tránh nesting sâu, dễ maintain.

  • Block: component độc lập — .card
  • Element: phần trong block — .card__title
  • Modifier: biến thể — .card--featured

Hiểu nhanh:
- var: function scope, có thể khai báo lại, dùng trước khi khai báo sẽ ra undefined.
- let: block scope (if, for, {}), cho phép gán lại.
- const: block scope, không cho gán lại biến.

Ví dụ:
const user = { name: "An" } vẫn sửa được user.name vì bạn đang sửa thuộc tính, không phải gán lại biến user.

Cho người mới: mặc định dùng const, khi cần đổi giá trị thì dùng let, tránh var.

JavaScript có 7 primitive types:
- string
- number
- boolean
- null
- undefined
- symbol
- bigint

Primitive được copy theo giá trị.
Ví dụ: let a = 5; let b = a; b = 10; thì a vẫn là 5.

Khác với object và array (copy theo tham chiếu).
Mẹo nhớ: typeof null trả về "object" là bug lịch sử.

  • == (loose): so sánh sau khi JavaScript tự ép kiểu.
  • === (strict): so sánh cả giá trị và kiểu, không ép kiểu.

Ví dụ:
- 0 == "0" -> true
- 0 === "0" -> false

Best practice cho người mới: dùng === gần như mọi lúc.

Type coercion là việc JavaScript tự đổi kiểu khi chạy phép toán.

Ví dụ dễ gặp:
- "5" + 3 -> "53" (nối chuỗi)
- "5" - 3 -> 2 (ép sang number)

Vì vậy code có thể cho kết quả bất ngờ nếu không kiểm soát kiểu.
Cách an toàn: dùng === và ép kiểu tường minh bằng Number(), String(), Boolean().

Falsy là giá trị thành false khi đưa vào điều kiện if. Có đúng 8 giá trị:
false, 0, -0, 0n, "", null, undefined, NaN.

Mọi giá trị khác là truthy, kể cả:
- "0"
- []
- {}

Mẹo: dùng Boolean(value) hoặc !!value để kiểm tra rõ ràng.

Hoisting nghĩa là JavaScript xử lý phần khai báo trước khi chạy code theo dòng.

Hiểu theo hành vi:
- var: được hoist và khởi tạo undefined.
- function declaration: hoist cả thân hàm, gọi trước vẫn được.
- let/const: cũng hoist nhưng chưa khởi tạo, truy cập sớm sẽ lỗi (TDZ).

javascript
console.log(a); // undefined (var hoisted)
console.log(b); // ReferenceError (let TDZ)
var a = 1;
let b = 2;

fn(); // hoạt động bình thường
function fn() { return "hoisted"; }

Vì vậy var có thể trông như chạy được nhưng dễ gây bug khó debug.

  • Function scope: biến var sống trong toàn bộ hàm chứa nó.
  • Block scope: biến let/const chỉ sống trong cặp {} gần nhất.

Ví dụ:
for (var i = 0; i < 3; i++) {}
ra ngoài vẫn dùng được i (giá trị 3).

for (let j = 0; j < 3; j++) {}
ra ngoài thì j không tồn tại.

Vì vậy code hiện đại ưu tiên let/const.

Ba cách định nghĩa hàm trong JS, khác nhau về hoisting và this binding:

1) Function declaration
function sum(a, b) { return a + b }
- Có thể gọi trước chỗ khai báo (hoisting).

2) Function expression
const sum = function(a, b) { return a + b }
- Chỉ gọi được sau khi gán.

3) Arrow function
const sum = (a, b) => a + b
- Ngắn gọn, không có this riêng, không dùng làm constructor.

Cho người mới: bắt đầu bằng declaration hoặc expression, dùng arrow nhiều cho callback.

Callback là hàm truyền vào hàm khác để được gọi sau.

Ví dụ:
setTimeout(() => console.log("xong"), 1000)
Hàm arrow bên trong là callback.

Vì JavaScript chạy một luồng (single thread), callback giúp:
- không chặn app khi chờ API hoặc file
- làm việc bất đồng bộ (async)

Hiện nay ta thường viết async bằng Promise và async/await, nhưng callback vẫn là nền tảng cần hiểu.

Trong strict mode, this ở hàm thông thường là undefined thay vì global object (window/global).

Điều này giúp phát hiện lỗi khi vô tình gọi hàm như function thay vì method.

javascript
function foo() { console.log(this); }
foo();                    // non-strict: Window (browser)
'use strict';
function bar() { console.log(this); }
bar();                    // strict: undefined

Kích hoạt bằng 'use strict'; ở đầu file hoặc hàm.

ES modules tự động bật strict mode.

Trong class, this trong constructor và methods trỏ đến instance được tạo.

Tuy nhiên, khi truyền method như callback, mất this binding.

javascript
class Counter {
  count = 0;

  // Class field arrow: auto-binds this
  increment = () => { this.count++; };
}

const c = new Counter();
const { increment } = c; // destructure method
increment(); // OK — this vẫn là Counter instance
console.log(c.count); // 1

Giải pháp: dùng arrow function trong class fields (tự bind), bind trong constructor, hoặc .bind() khi truyền callback.

__proto__ là instance link — nằm trên object instance. prototype là blueprint — nằm trên constructor function.

Khi dùng new Foo(), object mới có __proto__ trỏ tới Foo.prototype.

javascript
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() { return 'Woof'; };

const d = new Dog('Rex');
d.__proto__ === Dog.prototype; // true
Object.getPrototypeOf(d) === Dog.prototype; // true (cách khuyến nghị)

Trong code thực tế, dùng Object.getPrototypeOf(obj) thay vì truy cập __proto__ trực tiếp.

Destructuring cho phép giải nén giá trị từ array/object vào biến riêng lẻ.

Hỗ trợ default values, alias, và nested destructuring.

javascript
// Object destructuring:
const { name, age = 18, address: { city } } = user;

// Array destructuring (bỏ qua phần tử):
const [first, , third] = [1, 2, 3];

// Trong function parameter:
function greet({ name, role = 'user' }) {
  return `Hi ${name} (${role})`;
}

Spread mở rộng iterable thành từng phần tử (dùng trong function calls, array literals, object literals).

  • Rest thu thập nhiều phần tử thành array (dùng trong function parameters).
  • Cú pháp giống nhau nhưng ngữ cảnh ngược nhau.
javascript
// Spread: "mở ra"
const arr = [1, 2, 3];
console.log(Math.max(...arr)); // 3
const merged = [...arr1, ...arr2];
const copy = { ...obj, extra: true };

// Rest: "thu lại"
function sum(first, ...rest) { // rest là mảng
  return first + rest.reduce((a, b) => a + b, 0);
}

Template literals dùng backtick thay dấu nháy, hỗ trợ: interpolation ${expression}, multi-line strings, tagged templates.

Tagged templates là function nhận template strings và expressions làm arguments.

javascript
// Interpolation + multi-line:
const msg = `Hello ${name},
Welcome!`;

// Tagged template (styled-components pattern):
function css(strings, ...values) {
  return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
}
const color = 'blue';
const style = css`color: ${color}; font-size: 16px;`;

Class là cú pháp OOP rõ ràng hơn để tạo objects.

javascript
class Animal {
  #name; // private field
  constructor(name) {
    this.#name = name;
  }
  speak() {
    return this.#name + ' speaks';
  }
  static create(name) {
    return new Animal(name);
  }
}
const a = Animal.create('Cat');
a.speak(); // 'Cat speaks'

Khác function constructor: class không thể gọi không có new (throw TypeError), methods không enumerable (không xuất hiện trong for...in), và phải khai báo trước khi dùng (không hoisted).

Bên dưới vẫn dùng prototype chain — class chỉ là syntactic sugar.

for...in lặp qua enumerable property names của object (cả own lẫn inherited từ prototype chain). for...of lặp qua values của iterable objects (Array, String, Map, Set, Generator). for...of thường dùng cho arrays, for...in cho objects.

Tránh for...in với arrays vì có thể lặp qua inherited properties từ prototype. Dùng Object.hasOwn(obj, key) trong vòng lặp for...in để chỉ xử lý own properties khi cần.

ES modules là hệ thống module chính thức của JavaScript. export để export bindings, import để import. Static analysis: imports/exports được phân tích tại parse time, không thể dynamic (dùng import() cho dynamic). Module có own scope, strict mode by default.

Live bindings là đặc điểm quan trọng của ES modules: imported binding là reference đến exported value, không phải copy. Nếu module xuất let count = 0 và tăng nó, người import sẽ thấy giá trị mới nhất — khác CommonJS require() vốn copy value tại thời điểm require. Điều này quan trọng khi import singleton objects hay shared state.

includes(str) kiểm tra có chứa substring không (thay indexOf !== -1). startsWith(str) / endsWith(str) kiểm tra đầu/cuối chuỗi. padStart(len, char) / padEnd(len, char) thêm ký tự vào đầu/cuối cho đủ độ dài — ví dụ '5'.padStart(3, '0') cho ra '005', hữu ích để format số thứ tự. trimStart() / trimEnd() xóa whitespace một phía. replaceAll(old, new) thay thế tất cả occurrences, khác replace() chỉ thay lần đầu.

Những methods này giúp code ngắn và dễ đọc hơn nhiều so với regex.

Promise là "lời hứa" cho kết quả của tác vụ async (ví dụ gọi API). Nó có 3 trạng thái:
- pending: đang chờ
- fulfilled: thành công (resolve)
- rejected: thất bại (reject)

Một khi đã fulfilled hoặc rejected thì không quay lại pending.

javascript
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve('done'), 1000);
  // hoặc reject(new Error('failed')) nếu có lỗi
});
p.then(val => console.log(val)).catch(err => console.error(err));

Promise giúp tránh callback hell và làm luồng async dễ đọc hơn.

  • .then(...): chạy khi Promise thành công.
  • .catch(...): chạy khi có lỗi ở Promise trước đó.
  • .finally(...): luôn chạy sau cùng (dù thành công hay lỗi).

Ví dụ:

javascript
fetch(url)
  .then(r => r.json())
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => setLoading(false))

Mẹo cho người mới: dùng finally để dọn trạng thái UI như loading.

async/await là cú pháp viết Promise theo kiểu gần giống code đồng bộ, nên dễ đọc hơn chain .then().

Ví dụ:

javascript
async function loadUser() {
  try {
    const res = await fetch("/api/user")
    const data = await res.json()
    return data
  } catch (err) {
    console.error(err)
  }
}

Điểm cần nhớ:
- await chỉ dùng trong hàm async
- lỗi nên bắt bằng try/catch
- bản chất bên dưới vẫn là Promise.

DOM (Document Object Model) là biểu diễn dạng cây của HTML document, mỗi element là một node.

  • JavaScript tương tác qua document API: querySelector, createElement, appendChild, setAttribute...
  • DOM manipulation là synchronous, thay đổi DOM ngay lập tức reflect trên page.
javascript
const el = document.querySelector('.title');
el.textContent = 'Hello';

const btn = document.createElement('button');
btn.innerHTML = 'Click me';
document.body.appendChild(btn);

getElementById trả về element đầu tiên có id, nhanh nhất. querySelector trả về element đầu tiên khớp CSS selector. querySelectorAll trả về NodeList tất cả elements khớp (static, không live). getElementsByClassName và getElementsByTagName trả về HTMLCollection (live). querySelectorAll phổ biến nhất vì linh hoạt.

preventDefault() ngăn hành vi mặc định của browser (submit form, follow link, check checkbox) nhưng không dừng propagation. stopPropagation() ngăn event bubble/capture lên/xuống DOM tree nhưng không ngăn default behavior.

Có thể dùng cả hai cùng lúc.

Cookies gửi kèm mọi HTTP request (có thể dùng HttpOnly để ngăn JS access, Secure cho HTTPS only). LocalStorage/sessionStorage chỉ accessible qua JS, không gửi với requests.

Cookies cho session, auth tokens. LocalStorage cho user preferences, cache. Tránh lưu sensitive data trong localStorage vì XSS.

TypeScript là superset của JavaScript có static typing, compile sang JS.

Lợi ích: phát hiện lỗi tại compile time, IDE support tốt hơn (autocomplete, refactoring), code tự documentation hóa. Không thay thế JS mà enhance nó. Hữu ích nhất ở dự án lớn, team, và codebases cần maintain lâu dài.

Type annotation là khai báo kiểu tường minh cho biến, parameter, return value: let name: string = 'John'.

  • TypeScript cũng có type inference tự suy luận kiểu từ giá trị gán.
  • Annotation thường chỉ cần cho function parameters, return types, và khi inference không đủ thông tin.
typescript
function greet(name: string): string {
  return 'Hi ' + name;
}
let count: number = 0; // annotation thường dư (inference đủ)
let data: unknown;    // cần annotation vì chưa gán

any: tắt type checking, không an toàn, tránh dùng. unknown: type-safe any, phải kiểm tra type trước khi dùng, không thể assign cho typed variable mà không narrow. never: không bao giờ có giá trị, dùng cho hàm luôn throw, hoặc exhaustive checks. unknown >> any về safety.

Union type A | B: value có thể là A hoặc B — ví dụ string | number cho phép cả hai.

  • Intersection type A & B: value phải có tất cả properties của A lẫn B — ví dụ type AdminUser = User & { adminRole: string } tạo type có đủ fields của User cộng thêm adminRole.
  • Union dùng khi một giá trị có thể là nhiều loại (parameter linh hoạt), intersection dùng khi cần combine nhiều interface/type lại.
  • Bẫy: intersection của 2 primitive types không tương thích (như string & number) cho ra type never.

Type assertion overrides TS inference at compile time without any runtime check. value as Type nói với TS "tin tôi, tôi biết kiểu này".

  • Dùng khi TS không thể infer (DOM queries, JSON.parse).
  • Tránh dùng để cast bừa bãi — sẽ mất type safety. as unknown as Type (double assertion) là dấu hiệu code có vấn đề.
typescript
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const data = JSON.parse(json) as UserConfig; // vẫn cần validate runtime

Tuple là array với số lượng và kiểu phần tử cố định.

  • Phần tử có thể có tên.
  • Hỗ trợ optional (?) và rest elements.
  • Thường dùng cho function return nhiều giá trị, CSV rows, React useState.
typescript
type Point = [number, number];
type Entry = [key: string, value: number]; // named elements

const [count, setCount]: [number, React.Dispatch<React.SetStateAction<number>>]
  = useState(0);

// Hoặc đơn giản hơn:
const pair: [string, number] = ['age', 25];

strict: true bật nhiều checks: strictNullChecks (null/undefined không assign cho typed vars), strictFunctionTypes, strictPropertyInitialization, noImplicitAny, noImplicitThis, strictBindCallApply. Luôn bật strict: true cho dự án mới. Khi migrate JS sang TS có thể bật từng flag dần.

Lưu ý: TS 4.4 thêm useUnknownInCatchVariables vào strict — catch variable từ any chuyển thành unknown, bắt buộc narrow trước khi dùng. Điều này thường làm bối rối khi migrate dự án cũ.

  • Interface: có thể extend và merge (declaration merging), chỉ describe object shapes.
  • Type alias: linh hoạt hơn (union, intersection, primitives, tuples, conditional types), không merge.
  • Quy tắc: dùng interface cho public API và khi cần extend, dùng type cho unions/intersections và khi cần advanced type features.

Interface: interface Child extends Parent { ... } và extends nhiều: extends A, B.

  • Type: type Combined = A & B (intersection).
  • Interface có thể extend type và ngược lại.
typescript
interface Animal { name: string }
interface Pet extends Animal { owner: string }
interface Named { label: string }

interface RegisteredPet extends Pet, Named { id: number } // đa extends

// Type intersection tương đương:
type Combined = Pet & Named & { id: number };

extends kiểm tra compatibility (không thể re-declare incompatible property), còn & intersection merge tất cả.

Optional property (prop?: Type) có thể có hoặc không, type là Type | undefined khi access.

Khác với prop: Type | undefined — cái sau vẫn yêu cầu key có mặt khi tạo object. readonly property không thể reassign sau khởi tạo (chỉ compile time). Readonly<T> làm tất cả properties readonly.

typescript
interface User {
  id: number;
  nickname?: string;          // caller có thể bỏ qua
  bio: string | undefined;    // caller phải cung cấp key, nhưng value có thể undefined
  readonly createdAt: Date;   // không thể reassign
}

Partial<T> làm tất cả properties của T thành optional. Required<T> làm tất cả optional thành required.

Cả hai chỉ shallow.

typescript
interface User { id: number; name: string; bio?: string }

// Partial: dùng cho PATCH requests
function updateUser(id: string, data: Partial<User>) { /* ... */ }
updateUser('1', { name: 'New Name' }); // OK, không cần gửi toàn bộ

// Required: enforce tất cả fields sau validation
const validated: Required<User> = { id: 1, name: 'An', bio: 'Dev' };

Record<Keys, Value> tạo object type với specific keys và value type.

  • Khác index signature: Keys có thể là specific union (yêu cầu tất cả keys có mặt, không optional).
  • Rõ ràng hơn và type-safe hơn index signature.
typescript
const routes: Record<'home' | 'about' | 'contact', string> = {
  home: '/',
  about: '/about',
  contact: '/contact', // thiếu key này → Error
};

// Dùng với string key rộng hơn:
const cache: Record<string, unknown> = {};

Pick<T, K> tạo type chỉ với subset of properties K từ T: Pick<User, 'id' | 'name'> chỉ giữ lại id và name. Omit<T, K> là ngược lại: tạo type với tất cả properties trừ K, ví dụ Omit<User, 'password'> để không lộ mật khẩu trong response.

  • Hữu ích cho DTOs, API response shaping, và form state management khi chỉ cần một phần của type.
  • Mẹo: dùng Omit khi cần loại ít field, Pick khi cần giữ ít field — chọn cái nào viết ngắn hơn.

JSX (JavaScript XML) là cú pháp mở rộng của JavaScript cho phép viết HTML bên trong JavaScript.

  • React sử dụng JSX vì nó giúp mô tả UI trực quan hơn, kết hợp logic và markup trong cùng một file component.
  • Babel transpile JSX thành các lời gọi React.createElement() trước khi chạy trên trình duyệt.

JSX được Babel transpile thành React.createElement(type, props, ...children).

Ví dụ: <h1 className='title'>Hello</h1> trở thành React.createElement('h1', { className: 'title' }, 'Hello'). Kể từ React 17, có thể sử dụng JSX mà không cần import React nhờ transform tự động.

Có nhiều cách: toán tử ba ngôi isLoggedIn ? <Dashboard /> : <Login />, toán tử && isLoading && <Spinner />, hoặc if/else bên ngoài return.

  • Trong thực tế, ternary dùng cho toggle giữa 2 component, && dùng để hiển thị/ẩn một component.
  • Cần tránh count && <List /> vì nếu count=0, React render số 0 thay vì không render gì.

React Fragment (<React.Fragment> hoặc <>) cho phép nhóm nhiều phần tử mà không thêm node DOM thừa.

  • Sử dụng khi component cần trả về nhiều phần tử cùng cấp, ví dụ trong table rows hay danh sách.
  • Cú pháp rút gọn <>...</> không hỗ trợ thuộc tính key, cần dùng <React.Fragment key={id}> khi render danh sách.

Key là prop đặc biệt giúp React xác định phần tử nào trong danh sách đã thay đổi, được thêm hoặc bị xóa.

  • React sử dụng key để tối ưu hóa quá trình reconciliation, tránh re-render không cần thiết.
  • Key phải là chuỗi hoặc số duy nhất trong cùng cấp độ, không nên dùng index khi danh sách có thể thay đổi thứ tự.

JSX chỉ chấp nhận biểu thức (expressions) trong {}, không nhận câu lệnh (statements) như if, for, while vì JSX được transpile thành lời gọi hàm.

Ví dụ: {items.map(i => <li key={i.id}>{i.name}</li>)} hợp lệ, nhưng {for(...)} thì không. Dùng ternary cho điều kiện, map/filter để lặp. Nếu logic phức tạp, tính toán bên ngoài return rồi dùng biến trong JSX.

Trong JSX, tên event dùng camelCase (onClick, onChange, onSubmit) thay vì lowercase.

  • Handler là function reference, không phải string.
  • Để ngăn hành vi mặc định, gọi e.preventDefault() trong handler thay vì return false.
  • React sử dụng synthetic events để đảm bảo tính nhất quán giữa các trình duyệt.

Dùng {...props} để truyền tất cả thuộc tính của một object xuống component con.

Ví dụ: <Button {...buttonProps} /> tương đương với liệt kê từng prop riêng lẻ. Hữu ích khi cần forward props nhưng cần cẩn thận vì có thể truyền props không mong muốn.

React bỏ qua và không render null, undefined, false — đây là cơ chế cho conditional rendering.

  • Chuỗi rỗng '' cũng không hiển thị gì.
  • Tuy nhiên số 0 sẽ được render thành chữ số 0 trên màn hình — đây là lỗi hay gặp khi viết {count && <List />} và count bằng 0.
  • Cách fix: dùng ternary {count > 0 ? <List /> : null} để luôn trả về boolean hoặc null.

props.children là nội dung được truyền giữa thẻ mở và đóng của component.

Ví dụ: <Card><p>Nội dung</p></Card> thì Card nhận <p>Nội dung</p> qua props.children và render bằng {props.children}. Dùng để tạo wrapper component linh hoạt như Card, Modal, Layout mà không cần biết trước nội dung bên trong. Nếu không truyền gì, children là undefined — nên khai báo defaultProps hoặc kiểm tra trước khi render.

Controlled Component là component mà giá trị của form element (input, textarea, select) được kiểm soát bởi React state.

  • Mỗi thay đổi của input gọi onChange handler để cập nhật state, và value của input được bind với state.
  • Đây là cách được khuyến nghị vì cho phép validate và xử lý dữ liệu trước khi submit.

Stateless (presentational) component chỉ nhận props và render UI thuần túy, không giữ state nội bộ — dễ test vì output chỉ phụ thuộc vào input.

Ví dụ: <Button label='Save' onClick={fn} />. Stateful (container) component quản lý state và business logic, thường fetch data và pass xuống. Phân biệt này mờ dần từ khi Hooks ra đời — function component giờ có thể có state qua useState và vẫn được coi là một loại component duy nhất.

Component có thể return: JSX element, null (không render gì), array of elements, string, number, Portal, Fragment.

  • Return null hữu ích để ẩn component mà không thay đổi cấu trúc.
  • Array cần có key cho mỗi element.
  • String và number được render trực tiếp như text node.

Props là dữ liệu được truyền từ component cha xuống con, read-only và không thể thay đổi bởi component nhận.

  • State là dữ liệu nội bộ của component, có thể thay đổi theo thời gian và kích hoạt re-render.
  • Props giống như tham số hàm, state giống như biến cục bộ của component.

componentDidMount được gọi một lần duy nhất ngay sau khi component được gắn vào DOM lần đầu. Đây là nơi thích hợp để: fetch dữ liệu từ API, đăng ký event listeners, khởi tạo thư viện cần DOM như D3 hay Google Maps. Tương đương useEffect(() => { fetchData(); }, []) trong function component.

Lưu ý: gọi setState bên trong sẽ trigger thêm một lần render nhưng user không thấy trạng thái trung gian.

componentWillUnmount được gọi ngay trước khi component bị xóa khỏi DOM — đây là nơi bắt buộc phải cleanup để tránh memory leak.

Ví dụ: clearInterval(this.timerId), hủy WebSocket subscription, gọi controller.abort() để cancel fetch đang chạy. Không được gọi setState ở đây vì component không còn tồn tại. Tương đương hàm cleanup return () => clearInterval(id) trả về trong useEffect.

useState nhận giá trị khởi tạo và trả về mảng gồm state hiện tại và setter function.

  • Khi gọi setter, React schedule re-render với giá trị mới.
  • Setter có thể nhận giá trị mới trực tiếp hoặc updater function để tránh stale closure.
tsx
const Counter = () => {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1 trực tiếp</button>
      <button onClick={() => setCount(prev => prev + 1)}>+1 updater</button>
    </div>
  )
}

Dependency array kiểm soát khi nào effect chạy lại.

  • Không có array: chạy sau mỗi render.
  • Array rỗng []: chỉ chạy sau mount, tương đương componentDidMount.
  • Array có giá trị [a, b]: chạy khi a hoặc b thay đổi.
  • React ESLint plugin exhaustive-deps giúp phát hiện missing dependencies.

Trong thư mục app/, mỗi thư mục đại diện cho một route segment. page.tsx là component hiển thị cho route đó. [param] là dynamic segment. (group) là route group không ảnh hưởng URL. [...slug] là catch-all.

File conventions: page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx.

Export metadata object hoặc generateMetadata function từ page.tsx hoặc layout.tsx.

  • Tự động merge metadata từ parent layouts. export const metadata = { title: 'Page Title', description: '...' }. generateMetadata là async function nhận params để tạo dynamic metadata cho dynamic routes.
  • Thay thế cho next/head.

'use client' đặt ở đầu file để đánh dấu component là Client Component.

  • Cần thiết khi component dùng: useState, useEffect, event handlers, browser APIs.
  • Đặt boundary tại component cần interactivity, không cần wrap toàn app.
tsx
// components/like-button.tsx
'use client'  // ← bắt buộc vì dùng useState và onClick

import { useState } from 'react'

export function LikeButton({ initialCount }: { initialCount: number }) {
  const [count, setCount] = useState(initialCount)
  return (
    <button onClick={() => setCount(c => c + 1)}>
      ❤️ {count}
    </button>
  )
}

// app/post/page.tsx — Server Component, không cần 'use client'
import { LikeButton } from '@/components/like-button'

export default async function PostPage() {
  const post = await fetchPost()
  return (
    <article>
      <h1>{post.title}</h1>
      {/* Server Component chứa Client Component — OK */}
      <LikeButton initialCount={post.likes} />
    </article>
  )
}