Menu

Tham số tùy chọn trong TypeScript: giá trị mặc định và rest

Đánh dấu tham số TypeScript là tùy chọn bằng ?, gán giá trị mặc định cho nó, hoặc gom số lượng đối số bất kỳ bằng tham số rest. Gồm quy tắc thứ tự, bỏ qua so với undefined, options object và tham số tùy chọn trong kiểu hàm.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Thêm ? sau tên tham số để biến nó thành tùy chọn. Nơi gọi có thể bỏ qua tham số này, và bên trong hàm kiểu của nó có chứa undefined.

TypeScript kiểm tra số lượng đối số, nên nếu không có ? thì lời gọi đầu tiên sẽ là lỗi biên dịch: Expected 2 arguments, but got 1. (TS2554).

Tham số tùy chọn có thể là undefined

Vì nơi gọi có thể bỏ qua nó, kiểu của tham số tùy chọn bên trong hàm là T | undefined. Khi bật strictNullChecks, bạn phải xử lý trường hợp undefined trước khi dùng nó như một T.

Optional chaining (?.) và nullish coalescing (??) là những công cụ thường dùng ở đây. Khi giá trị dự phòng là cố định, tham số mặc định sẽ ngắn gọn hơn.

Giá trị mặc định của tham số

Giá trị mặc định làm tham số trở thành tùy chọn với nơi gọi và cho nó một kiểu không có undefined bên trong hàm. Kiểu được suy ra từ giá trị mặc định, nên thường không cần chú thích kiểu.

Nơi gọi thấy chữ ký repeat(text: string, times?: number, separator?: string). Giá trị mặc định tuân theo quy tắc của JavaScript: chúng được áp dụng khi đối số là undefined, dù bị bỏ qua hay được truyền tường minh, và không áp dụng khi đối số là null. Biểu thức mặc định có thể dùng các tham số đứng trước: function range(start: number, end = start + 10).

Quy tắc thứ tự tham số

Khai báoBiên dịch được?Ghi chú
(a: number, b?: number)CóTham số tùy chọn đặt ở cuối
(a?: number, b: number)KhôngTS1016: A required parameter cannot follow an optional parameter.
(a = 0, b: number)CóNhưng nơi gọi phải viết f(undefined, 5) để dùng giá trị mặc định
(a: number, ...rest: number[])CóTham số rest luôn đứng cuối
(a?: number, ...rest: number[])CóTham số tùy chọn đứng trước rest là hợp lệ

Tham số có mặc định đứng trước tham số bắt buộc là hợp lệ nhưng bất tiện. Với nơi gọi, kiểu của nó trở thành number | undefined, và chẳng ai thích viết undefined để giữ chỗ. Nếu cần một tham số đầu linh hoạt, hãy dùng options object hoặc nạp chồng hàm (function overloading).

Bỏ qua và undefined

x?: number và x: number | undefined trông giống nhau và có cùng kiểu bên trong hàm. Chúng khác nhau với nơi gọi: cái đầu có thể bỏ qua, cái sau bắt buộc phải truyền.

Dùng | undefined cho đối số bắt buộc nhưng được phép không có giá trị, để mọi nơi gọi đều phải cân nhắc nó. Dùng ? khi bỏ qua nó là một lời gọi bình thường. (Thông báo lỗi thật sự ghi "1 arguments": đó là cách diễn đạt của TypeScript.)

Tham số rest

Tham số rest, ...name: T[], gom số lượng đối số bất kỳ vào một mảng. Nó phải là tham số cuối cùng.

Spread một mảng vào các tham số cố định thì chặt chẽ hơn. Tham số rest chấp nhận spread từ bất kỳ number[] nào, nhưng hàm khai báo (a: number, b: number) chỉ chấp nhận spread từ một tuple, vì TypeScript phải biết độ dài:

function point(x: number, y: number) { return { x, y }; }

const list = [3, 4];            // number[]
point(...list);
// error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.

const pair = [3, 4] as const;   // readonly [3, 4]
point(...pair);                 // fine

Tham số rest cũng có thể có kiểu tuple, khi đó từng vị trí đều có kiểu riêng: ...args: [name: string, age?: number].

Options object

Khi một hàm có nhiều hơn hai hoặc ba tham số tùy chọn, nơi gọi khó nhớ vị trí của từng cái. Một options object với giá trị mặc định cho bạn các đối số có tên và không phụ thuộc thứ tự.

= {} ở cuối làm cả object trở thành tùy chọn. Nếu không có nó, fetchData("/a") là lỗi biên dịch (Expected 2 arguments, but got 1., TS2554), và trong JavaScript thuần lời gọi đó sẽ ném ra TypeError lúc chạy, vì destructuring cần một object để đọc.

Tham số tùy chọn trong kiểu callback

Trong một kiểu hàm, ? có nghĩa là "nơi gọi callback này có thể không truyền nó". Nó không có nghĩa là "callback có thể bỏ qua nó": một callback luôn có thể bỏ qua các tham số ở cuối. Vì vậy đừng đánh dấu tham số của callback là tùy chọn chỉ để cho phép handler nhận ít đối số hơn.

// Too loose: every handler must now cope with index being undefined
type Visit = (item: string, index?: number) => void;

// Right: the caller always passes both; handlers may use only item
type VisitStrict = (item: string, index: number) => void;
const log: VisitStrict = (item) => console.log(item);

Câu hỏi thường gặp

Làm sao để một tham số trở thành tùy chọn trong TypeScript?

Đặt ? sau tên của nó: function greet(name?: string). Nơi gọi có thể bỏ qua tham số này, và bên trong hàm nó có kiểu string | undefined, nên bạn cần kiểm tra trước khi dùng. Gán giá trị mặc định cho tham số, name = "there", cũng làm nó thành tùy chọn và loại undefined bên trong hàm.

Tham số tùy chọn có thể đứng trước tham số bắt buộc trong TypeScript không?

Không được với ?: (a?: number, b: number) là lỗi TS1016, "A required parameter cannot follow an optional parameter." Tham số có giá trị mặc định có thể đứng trước, nhưng khi đó nơi gọi phải truyền undefined một cách tường minh để dùng giá trị mặc định, nên trong thực tế tham số tùy chọn và tham số có mặc định luôn được đặt ở cuối.

x?: number và x: number | undefined khác nhau thế nào?

Bên trong hàm, cả hai đều là number | undefined. Khác biệt nằm ở nơi gọi: với x?: number có thể bỏ qua đối số, còn với x: number | undefined thì bắt buộc phải truyền, kể cả khi giá trị là undefined. Bỏ qua nó sẽ gây lỗi TS2554.

Truyền null có dùng giá trị mặc định của tham số không?

Không. JavaScript chỉ áp dụng giá trị mặc định khi đối số là undefined (bị bỏ qua hoặc được truyền tường minh). null là một giá trị, nên nó được giữ nguyên. Dù sao thì TypeScript cũng từ chối null cho tham số number khi bật strictNullChecks.

Làm sao truyền một mảng thành các đối số riêng lẻ trong TypeScript?

Dùng spread: fn(...args). Với hàm có số tham số cố định, mảng phải có kiểu tuple như [number, number] hoặc được tạo bằng as const; spread một number[] gây lỗi TS2556 vì không biết độ dài của nó. Spread vào tham số rest (...values: number[]) thì luôn hợp lệ.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu