Map trong TypeScript là Map có sẵn của JavaScript với khóa và giá trị có kiểu: Map<string, number> ánh xạ chuỗi sang số. Tạo một Map bằng new Map<K, V>(), rồi dùng set, get, has và delete. get trả về V | undefined, vì khóa có thể không tồn tại.
Chính các type argument làm cho Map hữu ích trong TypeScript: mọi lời gọi set đều được kiểm tra và mọi lời gọi get trả về kiểu giá trị. Dòng cuối là lỗi biên dịch (TS2345); @ts-expect-error giúp khối code vẫn chạy được.
Trang này nói về tập hợp Map. Nếu bạn đang tìm array.map(), nó được trình bày ở phần cuối.
Tạo một Map
Kiểu đến từ type argument, từ các mục ban đầu, hoặc từ chú thích kiểu. Các mục ban đầu là một mảng các tuple [key, value], hoặc bất cứ thứ gì sinh ra chúng.
Hai cái bẫy khi suy luận kiểu:
new Map()không có type argument và không có mục nào làMap<any, any>. Không gì bạn đưa vào hay lấy ra được kiểm tra. Hãy luôn viếtnew Map<K, V>().- Các mục có kiểu giá trị khác nhau không được suy ra thành union.
new Map([["a", 1], ["b", "x"]])là lỗi TS2769 (No overload matches this call). Hãy viết kiểu ra:new Map<string, number | string>([...]).
get trả về V | undefined
Map không thể hứa một khóa tồn tại, nên get có kiểu V | undefined. Với strict, bạn phải xử lý undefined trước khi dùng giá trị như một V:
index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
Các cách sửa, từ phổ biến nhất đến ít phổ biến nhất:
TypeScript không nhớ rằng has đã trả về true khi bạn gọi get sau đó. Kiểm tra trực tiếp kết quả của get vừa ngắn hơn vừa an toàn. Non-null assertion, stock.get("apples")!, làm lỗi biến mất nhưng không bảo vệ gì nếu khóa không tồn tại.
Các phương thức của Map và kiểu của chúng
| Thành viên | Kiểu trên Map<K, V> | Ghi chú |
|---|---|---|
new Map<K, V>(entries?) | Map<K, V> | entries: iterable gồm các [K, V] |
set(key, value) | this | Thêm hoặc thay thế; gọi nối tiếp được |
get(key) | V | undefined | undefined khi không có khóa |
has(key) | boolean | |
delete(key) | boolean | true nếu có gì đó bị xóa |
clear() | void | Xóa tất cả |
size | number | Là thuộc tính, không phải phương thức |
keys(), values() | iterator của K, V | Trải ra thành mảng: [...map.keys()] |
entries(), for...of | iterator của [K, V] | Theo thứ tự thêm vào |
forEach((value, key) => ...) | void | Lưu ý: giá trị đứng trước |
Lặp qua một Map
Map được duyệt theo thứ tự thêm vào. for...of trên Map trả về các tuple [key, value], có kiểu [K, V].
Đặt lại một khóa đã có sẽ cập nhật giá trị nhưng giữ nguyên vị trí ban đầu của nó trong thứ tự.
Đối tượng làm khóa và đếm phần tử
Giá trị nào cũng làm khóa được, kể cả đối tượng và mảng. Khóa được so sánh như ===: hai đối tượng có cùng nội dung là hai khóa khác nhau. Map cũng là cách chuẩn để đếm hoặc nhóm các phần tử.
Để dùng nội dung của đối tượng làm khóa, hãy suy ra một khóa chuỗi hoặc số, chẳng hạn user.id hoặc `${x},${y}`.
Map, object và Record
Map<K, V> | Object / Record<string, V> | |
|---|---|---|
| Kiểu khóa | bất kỳ, so sánh như === | string (số thành chuỗi), symbol |
| Kiểu khi thiếu khóa | get trả về V | undefined | obj[key] là V trừ khi bật noUncheckedIndexedAccess |
| Thứ tự | thứ tự thêm vào | phần lớn theo thứ tự thêm vào, nhưng khóa dạng số nguyên đứng trước theo thứ tự tăng dần |
| Kích thước | map.size | Object.keys(obj).length |
| Thêm và xóa thường xuyên | được tối ưu cho việc này | không được tối ưu cho việc này |
| JSON | không trực tiếp (JSON.stringify(map) là "{}") | trực tiếp |
| Cú pháp literal, destructuring | không | có |
| Khóa kế thừa ngoài ý muốn | không có | "toString" in {} là true |
Quy tắc chung: dùng Map cho tập hợp có khóa là dữ liệu (id người dùng, từ, mục cache) và thay đổi lúc chạy; dùng kiểu object hoặc Record cho một tập khóa cố định đã biết và cho mọi thứ đi vào hoặc đi ra từ JSON. Trang dictionary so sánh index signature, Record và Map cho việc tra cứu theo khóa chuỗi.
Chuyển Map sang object và JSON
Các mục của Map không phải thuộc tính, nên JSON.stringify không nhìn thấy chúng. Hãy chuyển qua Object.fromEntries và Object.entries:
JSON.parse trả về any, nên as Record<...> khẳng định dữ liệu được kỳ vọng là gì. Đó không phải kiểm tra lúc chạy; hãy kiểm tra JSON không đáng tin trước khi tin vào kiểu đó.
Khai báo kiểu cho array.map()
Nhiều lượt tìm "typescript map" thực ra muốn nói phương thức mảng, vốn biến đổi từng phần tử và trả về một mảng mới. Kiểu của nó được suy ra từ callback, nên hiếm khi cần chú thích:
Chú thích kiểu trả về của callback ((u): Option => ...) là cách rõ ràng nhất để nêu kiểu kết quả: khi đó, một thuộc tính bị thiếu hoặc viết sai trong đối tượng trả về sẽ là lỗi biên dịch ngay tại callback.
Câu hỏi thường gặp
Tạo Map trong TypeScript như thế nào?
Truyền kiểu khóa và kiểu giá trị vào hàm khởi tạo: const ages = new Map<string, number>(). Khi có sẵn các mục ban đầu, kiểu được suy ra: new Map([["ada", 36]]) là Map<string, number>. Một new Map() trống trơn, không kiểu và không mục nào, là Map<any, any>, tức tắt hẳn việc kiểm tra, nên hãy luôn khai báo kiểu.
Vì sao Map.get trả về undefined trong TypeScript?
map.get(key) có kiểu V | undefined vì khóa có thể không tồn tại. TypeScript không liên kết một lời gọi map.has(key) trước đó với lời gọi get sau đó, nên kể cả sau has bạn vẫn phải xử lý undefined: lưu kết quả lại rồi kiểm tra, hoặc dùng giá trị mặc định với ??.
Map và object trong TypeScript khác nhau thế nào?
Map nhận khóa thuộc bất kỳ kiểu nào (kể cả đối tượng), giữ thứ tự thêm vào, có size, và được thiết kế cho việc thêm, xóa thường xuyên. Một đối tượng thường hoặc Record<string, V> chỉ có khóa chuỗi (và symbol), chuyển thẳng sang JSON được, và hỗ trợ cú pháp literal cùng destructuring. Hãy dùng Map cho tập hợp có khóa động, dùng object cho cấu trúc cố định và dữ liệu JSON.
Làm sao để chuyển Map sang object hoặc JSON trong TypeScript?
Object.fromEntries(map) biến một Map<string, V> thành đối tượng thường, sau đó JSON.stringify có thể chuyển nó sang JSON. Gọi JSON.stringify(map) trực tiếp trên Map cho ra "{}", vì các mục của Map không phải là thuộc tính. Chiều ngược lại là new Map(Object.entries(obj)).
Khai báo kiểu cho callback của array.map trong TypeScript như thế nào?
Thường thì không cần: items.map((item) => item.name) suy ra item từ mảng và kiểu kết quả từ giá trị callback trả về. Để ép một kiểu kết quả, hãy truyền nó làm type argument, items.map<string>(...), hoặc chú thích kiểu trả về của callback.