Необязательные свойства
В некоторых анкетах рядом с полем стоит пометка «по желанию»: «Возраст — по желанию». Такое поле можно оставить пустым, и анкету всё равно примут. Но тот, кто потом её читает, не может сразу взять возраст и что-то с ним посчитать: сначала он посмотрит, заполнено ли поле.
В TypeScript такое поле — необязательное свойство. Его отмечают знаком ? после имени:
type User = {
name: string;
age?: number;
};
const ann: User = { name: "Ann", age: 30 };
const bob: User = { name: "Bob" };
console.log(ann.age); // 30
console.log(bob.age); // undefinedРазберём пример по строкам:
age?: number— свойствоageможно не указывать, а если оно указано, в нём число.- У
annвозраст есть, и компилятор проверил, что это число. - У
bobвозраста нет, и это не ошибка — в отличие от пропущенногоname. ann.ageпечатает30, аbob.age—undefined: так JavaScript отвечает на чтение свойства, которого в объекте нет.
Что лежит в свойстве с ?
Прочитав bob.age, программа получает undefined. Поэтому TypeScript считает, что в необязательном свойстве лежит не number, а объединение number | undefined: может прийти число, а может не прийти ничего.
С таким объединением действует обычное правило объединений: можно только то, что подходит обоим вариантам. У undefined методов нет, поэтому сразу вызвать метод числа нельзя:
type User = { name: string; age?: number };
function printAge(u: User) {
console.log(u.age.toFixed(1)); // ✗
}error TS18048: 'u.age' is possibly 'undefined'.Компилятор говорит: «u.age может оказаться undefined». И он прав: если бы этот JavaScript запустили для пользователя без возраста, программа упала бы на этой строке с ошибкой TypeError: Cannot read properties of undefined (reading 'toFixed').
Сложение тоже не пройдёт: на u.age + 1 tsc выдаст ту же ошибку TS18048, хотя JavaScript молча вернул бы NaN.
Здесь аналогия с анкетой перестаёт работать. Пустое поле на бумаге видно сразу, а функция не знает, какой объект ей передадут: это решится только при запуске. Поэтому проверку пишут в коде, и после неё тип меняется. Пройдите функцию по шагам и следите за типом u.age до проверки и после неё:
1type User = { name: string; age?: number };подсказка редактораage: number | undefined2 3function printAge(u: User) {4 const value = u.age;5 console.log(u.age.toFixed(1));6 if (u.age !== undefined) {7 console.log(u.age.toFixed(1));8 }9 const shown = u.age ?? 0;10 console.log(shown);11}
Знак ? добавил к типу свойства вариант undefined.
Сначала проверить, потом использовать
Превратить number | undefined в number можно двумя способами.
Проверка через if
type User = { name: string; age?: number };
function ageText(u: User): string {
if (u.age !== undefined) {
return u.age.toFixed(1);
}
return "no age";
}
console.log(ageText({ name: "Ann", age: 30 })); // 30.0
console.log(ageText({ name: "Bob" })); // no ageРазберём, что видит компилятор:
- В условии
u.age !== undefinedуu.ageтипnumber | undefined. - Внутри
ifвариантundefinedуже отсеян, иu.age— этоnumber, поэтомуtoFixedработает. - После
ifфункция возвращает запасной текст для тех, у кого возраста нет. - Программа печатает
30.0иno age.
Проверка через if подходит, когда для пустого поля нужна своя ветка: другой текст или другое действие.
Значение по умолчанию через ??
Оператор ?? берёт значение справа, когда слева null или undefined. В записи const shown = u.age ?? 0; у shown тип number: вместо undefined подставится 0, и проверять больше нечего.
Этот способ подходит, когда у пустого поля есть разумное значение по умолчанию. Но запасное значение не должно вводить в заблуждение: возраст 0 в отчёте выглядит как настоящий, и тогда лучше отдельная ветка с if.
Частая ошибка
Необязательное свойство записали через объединение:
type User = { name: string; age: number | undefined };
const bob: User = { name: "Bob" }; // ✗error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.Запись age: number | undefined значит другое: свойство обязательно, хотя значение в нём может быть undefined. Поэтому литерал без age не проходит, и пришлось бы писать { name: "Bob", age: undefined }. Если поле можно не указывать, нужен знак ?: age?: number. Разницу между этими двумя записями любят спрашивать на собеседованиях.
Главное
age?: number— свойство, которое можно не указывать в объекте.- Читается оно как
number | undefined, и без проверки нельзя ни вызвать метод числа, ни посчитать с ним: ошибкаTS18048. - Внутри
if (u.age !== undefined)уu.ageтипnumber, аu.age ?? 0сразу даётnumber. age: number | undefined— обязательное свойство, а не необязательное.