Пошук уроків, статей та іншого контенту
Налаштовуємо параметри типу за замовчуванням і розглядаємо правила їх поєднання з обов’язковими параметрами.
Параметр типу за замовчуванням — це generic-параметр, для якого вказано тип, що використовуватиметься, якщо під час використання generic не передати власний тип.
Синтаксис:
type Box<T = string> = {
value: T;
};Тепер Box можна використати двома способами:
const textBox: Box = {
value: "TypeScript",
};
const numberBox: Box<number> = {
value: 42,
};У першому випадку T дорівнює string, оскільки це значення за замовчуванням. У другому випадку явно переданий number замінює значення за замовчуванням.
Параметри типу за замовчуванням роблять generic-параметр необов’язковим.
Параметри типу за замовчуванням можна використовувати у функціях:
function createValue<T = string>(value?: T): T | undefined {
return value;
}
const text = createValue("hello");
// type: string | undefined
const number = createValue<number>(42);
// type: number | undefined
const empty = createValue();
// type: string | undefinedУ виклику createValue("hello") TypeScript виводить T як string з аргументу функції.
У виклику createValue<number>(42) тип T вказано явно.
У виклику createValue() TypeScript не має значення, з якого можна вивести T, тому використовує тип за замовчуванням string.
Важливо: значення за замовчуванням для параметра типу не є значенням за замовчуванням для параметра функції. Це різні механізми.
function createValue<T = string>(value?: T): T | undefined {
return value;
}Тут T = string визначає тип, а value?: T визначає, що аргумент value можна не передавати.
Generic-параметри без значення за замовчуванням є обов’язковими. Параметри зі значенням за замовчуванням є необов’язковими.
Обов’язковий параметр потрібно розміщувати перед необов’язковим:
interface ApiResponse<TData, TError = { message: string }> {
data?: TData;
error?: TError;
}Тут:
TData — обов’язковий параметр;
TError — необов’язковий параметр зі значенням за замовчуванням.
Використання:
type User = {
id: number;
name: string;
};
const successResponse: ApiResponse<User> = {
data: {
id: 1,
name: "Olena",
},
};
const customErrorResponse: ApiResponse<
User,
{ code: number; message: string }
> = {
error: {
code: 404,
message: "User not found",
},
};У ApiResponse<User> другий параметр не вказано, тому TError отримує значення:
{
message: string;
}У другому прикладі для TError передано власний тип.
Після параметра зі значенням за замовчуванням не можна розміщувати обов’язковий параметр.
Некоректний приклад:
// Помилка:
// Required type parameters must not follow optional type parameters.
type Invalid<T = string, U> = {
first: T;
second: U;
};Причина в неоднозначності: TypeScript не може пропустити T і передати значення тільки для U.
Правильний порядок:
type Valid<T, U = string> = {
first: T;
second: U;
};Якщо generic має кілька параметрів, усі параметри зі значеннями за замовчуванням повинні бути в кінці:
type Triple<A, B = string, C = number> = {
first: A;
second: B;
third: C;
};У цьому прикладі:
type A = Triple<boolean>;
// A: { first: boolean; second: string; third: number }
type B = Triple<boolean, Date>;
// B: { first: boolean; second: Date; third: number }
type C = Triple<boolean, Date, bigint>;
// C: { first: boolean; second: Date; third: bigint }Типи можна явно передавати лише зліва направо. Не можна пропустити параметр посередині та вказати наступний.
type Config<T, Format = "json", Debug = false> = {
value: T;
format: Format;
debug: Debug;
};
type First = Config<string>;
// T = string
// Format = "json"
// Debug = false
type Second = Config<string, "xml">;
// T = string
// Format = "xml"
// Debug = false
type Third = Config<string, "xml", true>;
// T = string
// Format = "xml"
// Debug = trueУявного синтаксису для пропуску Format не існує:
// Некоректно:
// type Invalid = Config<string, , true>;Якщо потрібно змінити третій параметр, доведеться також явно вказати другий:
type Correct = Config<string, "json", true>;Для функцій TypeScript спочатку намагається вивести тип із переданих аргументів. Значення за замовчуванням використовується тоді, коли відповідний тип не вдалося вивести.
function createPair<T = string, U = number>(
first?: T,
second?: U,
): [T | undefined, U | undefined] {
return [first, second];
}
const pair1 = createPair("id", 42);
// type: [string | undefined, number | undefined]
const pair2 = createPair<boolean>(true);
// T = boolean
// U = number за замовчуванням
const pair3 = createPair();
// T = string за замовчуванням
// U = number за замовчуваннямУ createPair("id", 42) обидва типи виводяться з аргументів, тому значення за замовчуванням не використовуються.
У createPair<boolean>(true) явно вказано лише T. Параметр U залишився необов’язковим і отримав тип number.
Параметр типу за замовчуванням може використовувати попередній параметр:
type Collection<T, ItemList = T[]> = {
value: T;
items: ItemList;
};
type Numbers = Collection<number>;
// {
// value: number;
// items: number[];
// }
type NumberSet = Collection<number, Set<number>>;
// {
// value: number;
// items: Set<number>;
// }У Collection<number> параметр ItemList автоматично стає number[], оскільки його значення за замовчуванням — T[].
Це корисно, коли типовий зв’язок між параметрами можна описати наперед, але розробнику потрібно залишити можливість його змінити.
Параметр типу зі значенням за замовчуванням може мати обмеження через extends.
Значення за замовчуванням повинно відповідати цьому обмеженню:
type Select<T, K extends keyof T = keyof T> = Pick<T, K>;
type User = {
id: number;
name: string;
active: boolean;
};
type AllUserFields = Select<User>;
// K = "id" | "name" | "active"
type UserName = Select<User, "name">;
// K = "name"Тут K може бути лише ключем типу T. За замовчуванням K дорівнює keyof T, що відповідає обмеженню.
Некоректне значення за замовчуванням спричинить помилку:
// Помилка: "boolean" не відповідає обмеженню "string".
type Invalid<T = boolean> = T extends string ? T : never;Окремо обмеження для T може гарантувати, що значення за замовчуванням для наступного параметра також буде коректним:
type Entity = {
id: number;
};
type EntityMap<
T extends Entity,
K extends keyof T = "id",
> = {
key: K;
value: T;
};
type UserEntity = Entity & {
name: string;
};
type UserMap = EntityMap<UserEntity>;
// K = "id"Нижче наведено повний приклад із результатом операції. Тип помилки має значення за замовчуванням, але його можна замінити власним типом.
type DefaultError = {
message: string;
};
type Result<T, E = DefaultError> =
| {
ok: true;
value: T;
}
| {
ok: false;
error: E;
};
function success<T>(value: T): Result<T> {
return {
ok: true,
value,
};
}
function failure<TError = DefaultError>(
error: TError,
): Result<never, TError> {
return {
ok: false,
error,
};
}
type User = {
id: number;
name: string;
};
const successfulUser = success<User>({
id: 1,
name: "Olena",
});
const defaultFailure = failure({
message: "Користувача не знайдено",
});
const customFailure = failure<{
code: number;
message: string;
}>({
code: 404,
message: "Користувача не знайдено",
});
function printResult<T, E = DefaultError>(result: Result<T, E>): void {
if (result.ok) {
console.log("Значення:", result.value);
} else {
console.log("Помилка:", result.error);
}
}
printResult(successfulUser);
printResult(defaultFailure);
printResult(customFailure);У цьому прикладі:
Result<T, E = DefaultError> має обов’язковий T і необов’язковий E;
success<User> використовує стандартний тип помилки, навіть якщо помилки в результаті немає;
failure(...) може вивести тип помилки з аргументу;
якщо передати власний тип через failure<...>, він замінить DefaultError.
// Некоректно
type Invalid<T = string, U> = {
first: T;
second: U;
};Спочатку потрібно розміщувати всі обов’язкові параметри, а потім — параметри зі значеннями за замовчуванням.
type Options<T, Format = "json", Debug = false> = {
value: T;
format: Format;
debug: Debug;
};
// Некоректно: пропущено Format
// type Invalid = Options<string, , true>;Передати параметри можна лише послідовно, починаючи з першого.
function wrap<T = string>(value: T): T {
return value;
}
const result = wrap(123);
// type: number, а не stringЯкщо TypeScript може вивести тип із аргументу, він використовує виведений тип. Значення за замовчуванням застосовується лише тоді, коли тип не було виведено або його не передали явно.
// Некоректно
type Invalid<T extends string = number> = T;Тип number не відповідає обмеженню extends string.
Generic-параметр можна оголосити зі значенням за замовчуванням: T = string.
Параметри без значення за замовчуванням є обов’язковими.
Обов’язкові параметри потрібно розміщувати перед необов’язковими.
Після першого параметра зі значенням за замовчуванням не можна додавати обов’язкові параметри.
Параметри можна пропускати лише з кінця, але не посередині.
Якщо тип можна вивести з аргументів функції, TypeScript надає перевагу виведеному типу.
Значення за замовчуванням повинно відповідати обмеженню параметра типу.
Параметр за замовчуванням може залежати від попередніх параметрів типу.