When you pass an array literal or object literal into a generic function, TypeScript defaults to inferring the widest possible type. An array ['a', 'b'] infers as string[] rather than readonly ['a', 'b'], losing precise type information.
Khi bạn truyền một mảng literal hoặc object literal vào một hàm generic, TypeScript mặc định suy luận kiểu rộng nhất có thể. Một mảng ['a', 'b'] sẽ được suy luận là string[] thay vì readonly ['a', 'b'], làm mất đi thông tin kiểu chính xác.
1function makeRoute<T>(path: T) {2 return { path };3}45const r = makeRoute(["users", "profile"]);6// r: { path: string[] } — literal types are lost!78// To preserve them, callers had to write 'as const' themselves:9const r2 = makeRoute(["users", "profile"] as const);10// r2: { path: readonly ["users", "profile"] } — correct, but inconvenientBefore TypeScript 5.0, the only solution was to require callers to write as const, placing the burden on API consumers and making it easy to forget.
Trước TypeScript 5.0, giải pháp duy nhất là yêu cầu người gọi viết as const, điều này tạo ra gánh nặng cho người dùng API và dễ bị quên mất.
TypeScript 5.0 introduced the const modifier for type parameters. Writing function f<const T> makes TypeScript infer T as if every argument had as const — preserving literal types, tuple shapes, and readonly.
TypeScript 5.0 giới thiệu modifier const cho tham số kiểu. Khi bạn viết function f<const T>, TypeScript suy luận kiểu của T như thể tất cả các đối số đều có as const — tức là nó giữ nguyên literal types, tuple shapes, và readonly.
1// Before: without const — inference widens to string[]2function makeRoute<T>(path: T) {3 return { path };4}5const r1 = makeRoute(["users", "profile"]);6// r1: { path: string[] }78// After: with const — inference preserves the tuple literal9function makeRouteConst<const T>(path: T) {10 return { path };11}12const r2 = makeRouteConst(["users", "profile"]);Const type parameters shine in type-safe builder APIs, route definitions, and config DSLs — anywhere literal types are needed for precise union types or key-level inference.
const type parameters đặc biệt mạnh mẽ khi xây dựng các type-safe builder APIs, route definitions, và config DSLs — nơi mà literal types là cần thiết để tạo ra các union types chính xác hoặc để truy cập key-level inference.
1// Route builder — each segment's literal type is preserved2function defineRoutes<const T extends readonly string[]>(segments: T): T {3 return segments;4}56const routes = defineRoutes(["home", "about", "contact"]);7// routes: readonly ["home", "about", "contact"]89type Route = (typeof routes)[number];10// Route: "home" | "about" | "contact" — a precise union, not just string!1112// Without const, Route would be: string1314// Navigation is now type-safe:15function navigate(route: Route) {16 console.log("navigating to", route);17}1819navigate("home"); // OK20navigate("settings"); // Error: "settings" is not assignable to Route1// Config DSL — build an object where each key is known at the type level2function createForm<const T extends Record<string, { label: string; required: boolean }>>(3 fields: T4): { validate: () => Partial<{ [K in keyof T]: string }> } {5 return {6 validate() {7 // runtime validation logic here8 return {} as Partial<{ [K in keyof T]: string }>;9 },10 };11}1213const loginForm = createForm({14 email: { label: "Email", required: true },15 password: { label: "Password", required: true },16});1718// TypeScript knows the exact keys: "email" | "password"19type LoginFields = keyof typeof loginForm; // never — example only; the real type is on the return valueConst type parameters and as const assertions work naturally together — both push TypeScript toward preserving literal types. However, there are important situations where const type parameters do NOT help.
const type parameter và as const assertion phối hợp nhau một cách tự nhiên — chúng đều hướng TypeScript đến việc giữ nguyên literal types. Tuy nhiên có những tình huống quan trọng mà const type parameter KHÔNG giúp được.
1// const T and as const are complementary, not mutually exclusive2function identity<const T>(value: T): T {3 return value;4}56// Passing an already-as-const value — both work fine together7const config = { mode: "dark" } as const;8const result = identity(config);9// result: { readonly mode: "dark" }1011// Passing a mutable variable — const T does NOT re-infer it12const mutableArr = ["x", "y"]; // already inferred as string[]13const r = identity(mutableArr);14// r: string[] — const T only affects the call-site literal, not a pre-existing variableconst modifier only affects inference for literals passed directly at the call site. If you store the value in a variable first, TypeScript has already inferred that variable's type — const T cannot change it. In that case, as const on the variable declaration is still needed.const modifier trên tham số kiểu chỉ ảnh hưởng đến việc suy luận kiểu TẠI CALL SITE khi truyền một literal trực tiếp. Nếu bạn lưu giá trị vào biến trước rồi mới truyền vào, TypeScript đã suy luận kiểu của biến đó rồi — const T không có tác dụng gì thêm. Trong trường hợp đó, as const vẫn cần thiết.
Const type parameters are not a universal solution. There are cases where they do not work as expected or do not solve the problem.
const type parameters không phải là giải pháp vạn năng. Có một số trường hợp chúng không hoạt động như mong đợi hoặc không giải quyết được vấn đề.
1// 1. Mutable object PROPERTIES are still widened in some patterns2function wrap<const T>(value: T) {3 return { value }; // The wrapper object is NOT const — value is still preserved though4}56// 2. Generic constraints can fight const inference7function pickFirst<const T extends string[]>(arr: T): T[0] {8 return arr[0] as T[0];9}10const first = pickFirst(["a", "b", "c"]);11// first: "a" — works correctly here12T as if as const were applied, preserving literals and tuplesfunction f<const T> khiến TypeScript suy luận T như thể có as const, giữ nguyên literal và tuple types
as const — const T moves that burden to the API definitionCallers không cần viết as const nữa — const T đưa gánh nặng từ người dùng sang định nghĩa API
Đặc biệt hữu ích cho route definitions, builder APIs, config DSLs nơi literal types tạo ra union types chính xác
Chỉ ảnh hưởng đến literal được truyền trực tiếp tại call site — không ảnh hưởng đến biến đã được suy luận trước đó
const T and as const can be used together — they are complementary, not mutually exclusiveconst T và as const có thể dùng cùng nhau — chúng bổ sung cho nhau, không loại trừ nhau
Tính năng này có từ TypeScript 5.0 — kiểm tra phiên bản trước khi dùng