Необязательные свойства (?)
Optional properties — как сделать свойства интерфейса необязательными
Цели урока
- Понять, что такое необязательные свойства
- Освоить синтаксис с символом ?
- Научиться проверять наличие необязательных свойств
- Понять, когда использовать optional properties
- Изучить операторы optional chaining и nullish coalescing
Зачем нужны необязательные свойства?
В реальных приложениях не все данные всегда доступны. Например, при регистрации пользователя some поля могут быть обязательными (имя, email), а некоторые — необязательными (номер телефона, адрес, дата рождения). Если мы опишем интерфейс без учёта необязательных полей, то столкнёмся с проблемой: TypeScript будет требовать указания всех свойств, даже тех, которые могут отсутствовать.
Представьте, что вы создаёте форму регистрации. Пользователь обязан указать имя и email, но номер телефона — по желанию. Если интерфейс требует все поля, вы не сможете создать объект пользователя без номера телефона, даже если пользователь его не указал.
Необязательные свойства решают эту проблему, позволяя указать, какие поля могут отсутствовать. TypeScript будет проверять наличие только обязательных свойств, а необязательные будет считать опциональными.
Это особенно важно при работе с внешними API, где структура ответов может варьироваться. Например, API может возвращать пользователя с полем «телефон», если он был указан при регистрации, и без него, если не был.
Синтаксис необязательных свойств
В TypeScript необязательные свойства обозначаются символом ? после имени свойства:
name: string; // обязательное свойство
email: string; // обязательное свойство
phone?: string; // необязательное свойство
address?: string; // необязательное свойство
dateOfBirth?: string; // необязательное свойство
avatar?: string; // необязательное свойство
}
Обратите внимание на символ ? после имени свойства. Это сигнал для TypeScript, что данное свойство может отсутствовать. Символ ? размещается между именем свойства и двоеточием.
Необязательные свойства всегда должны идти после обязательных. Это логично: TypeScript проверяет обязательные свойства primero, а затем необязательные. Если обязательное свойство отсутствует, компилятор выдаст ошибку.
Использование интерфейса с необязательными свойствами
Теперь мы можем создавать объекты, которые соответствуют интерфейсу, даже без необязательных свойств:
const user1: UserProfile = {
name: "Алексей",
email: "alexey@example.com"
};
// С одним необязательным свойством
const user2: UserProfile = {
name: "Мария",
email: "maria@example.com",
phone: "+7 (999) 123-45-67"
};
// Со всеми свойствами
const user3: UserProfile = {
name: "Дмитрий",
email: "dmitry@example.com",
phone: "+7 (999) 765-43-21",
address: "Москва, ул. Примерная, д. 1",
dateOfBirth: "1990-05-15",
avatar: "/avatars/user3.jpg"
};
// TypeScript выдаст ошибку только для обязательных полей:
// const user4: UserProfile = { phone: "+7..." };
// Ошибка! Отсутствуют обязательные свойства name и email
Все три объекта соответствуют интерфейсу UserProfile. Первый содержит только обязательные свойства, второй — одно необязательное, третий — все.
Проверка необязательных свойств
Поскольку необязательные свойства могут отсутствовать, TypeScript требует проверять их наличие перед использованием. Это важное правило безопасности:
console.log(`Отправляем SMS на ${phone}`);
}
function registerUser(user: UserProfile) {
// Ошибка: тип 'string | undefined' не может быть
// передан в качестве параметра типа 'string'
// sendSms(user.phone);
// Способ 1: Проверка с помощью if
if (user.phone) {
sendSms(user.phone);
}
// Способ 2: Проверка на undefined
if (user.phone !== undefined) {
sendSms(user.phone);
}
// Способ 3: Оператор optional chaining
console.log(user.phone?.length);
// Способ 4: Оператор nullish coalescing
const phone = user.phone ?? "Номер не указан";
console.log(`Телефон: ${phone}`);
// Способ 5: Значение по умолчанию
const phone2 = user.phone || "Не указан";
}
Оператор ?. (optional chaining) позволяет безопасно обращаться к свойствам, которые могут быть undefined. Если свойство отсутствует, выражение вернёт undefined вместо ошибки. Это современный и рекомендуемый способ работы с необязательными свойствами.
Взаимосвязь optional и union типов
Необязательные свойства на самом деле являются сокращённой записью union-типа Type | undefined:
interface UserA {
phone?: string;
}
interface UserB {
phone: string | undefined;
}
// Но есть важное отличие при проверке:
function test(a: UserA, b: UserB) {
// TypeScript знает, что phone может отсутствовать
// после проверки через if
if (a.phone) {
console.log(a.phone); // string (сузился)
}
// Для UserB phone всегда есть (даже если undefined)
console.log(b.phone); // string | undefined
}
Рекомендуется использовать ?, так как это более идиоматично и читаемо.кроме того, TypeScript лучше сужает типы для optional свойств при проверке через if.
Optional chaining в деталях
Оператор ?. (optional chaining) особенно полезен при работе с вложенными необязательными свойствами:
name: string;
address?: {
city: string;
street: string;
zipCode?: string;
};
}
const company: Company = { name: "TechCorp" };
// Без optional chaining — нужна проверка каждого уровня:
// if (company.address && company.address.zipCode) {
// console.log(company.address.zipCode);
// }
// С optional chaining — просто и безопасно:
const zipCode = company.address?.zipCode;
console.log(zipCode); // undefined
// Цепочка может быть длинной:
const cityLength = company.address?.city?.length;
console.log(cityLength); // undefined
// Optional chaining с вызовом методов:
const upperCity = company.address?.city?.toUpperCase();
console.log(upperCity); // undefined
// Optional chaining с индексацией:
const firstChar = company.address?.city?.[0];
console.log(firstChar); // undefined
Optional chaining значительно упрощает работу с вложенными необязательными свойствами, избавляя от множественных проверок на null/undefined. Это делает код более чистым и читаемым.
Nullish coalescing (??)
Оператор ?? (nullish coalescing) возвращает правый операнд, если левый равен null или undefined:
const a = null ?? "default"; // "default"
const b = undefined ?? "default";// "default"
const c = 0 ?? "default"; // 0 (не "default"!)
const d = "" ?? "default"; // "" (не "default"!)
const e = false ?? "default";// false (не "default"!)
// С оператором ||:
const f = 0 || "default"; // "default" (0 — falsy!)
const g = "" || "default"; // "default" ("" — falsy!)
// Практическое применение:
interface Config {
timeout?: number;
retries?: number;
}
function connect(config: Config) {
// Правильно: ?? сохраняет 0 и false
const timeout = config.timeout ?? 5000;
const retries = config.retries ?? 3;
console.log(`Таймаут: ${timeout}, попытки: ${retries}`);
}
Используйте ?? вместо ||, когда нужно сохранить значение 0, false или пустую строку.
Практический пример: конфигурация
Одно из самых частых применений optional properties — описание конфигурационных объектов:
host: string; // обязательно
port: number; // обязательно
protocol?: string; // по умолчанию "http"
timeout?: number; // по умолчанию 5000
retries?: number; // по умолчанию 3
ssl?: boolean; // по умолчанию false
debug?: boolean; // по умолчанию false
}
// Минимальная конфигурация
const devConfig: ServerConfig = {
host: "localhost",
port: 3000,
debug: true
};
// Полная конфигурация
const prodConfig: ServerConfig = {
host: "api.example.com",
port: 443,
protocol: "https",
timeout: 10000,
retries: 5,
ssl: true,
debug: false
};
// Функция с значениями по умолчанию
function connect(config: ServerConfig): void {
const protocol = config.protocol ?? "http";
const timeout = config.timeout ?? 5000;
const retries = config.retries ?? 3;
const ssl = config.ssl ?? false;
console.log(`Подключение к ${protocol}://${config.host}:${config.port}`);
console.log(`Таймаут: ${timeout}мс, попытки: ${retries}, SSL: ${ssl}`);
}
Распространённые ошибки
// Объект 'user.phone'
// может быть undefined
// undefined если нет телефона
// Тип 'string | undefined'
// не может быть string
// string (всегда определён)
name?: string;
age: number;
}
// Optional перед обязательным!
age: number;
name?: string;
}
// Обязательные first, optional last
Когда использовать optional properties
Используйте optional properties, когда:
- Данные могут отсутствовать (номер телефона, адрес, аватар)
- Создаёте конфигурационные объекты с значениями по умолчанию
- Работаете с API, где ответы могут содержать разные наборы полей
- Описываете опции для функций или методов
- Создаёте формы, где не все поля обязательны
Не используйте optional properties, когда:
- Свойство всегда должно присутствовать в объекте
- Отсутствие свойства означает ошибку в данных
- Вы хотите гарантировать наличие данных для бизнес-логики
- Свойство критично для работы функции
Практическое задание
Расширьте интерфейс Book из предыдущего урока, добавив необязательные свойства для publisher, language и rating. Затем создайте функцию, которая безопасно выводит информацию о книге, используя optional chaining и nullish coalescing:
// 2. Создайте функцию formatBookInfo(book: Book): string
// 3. Используйте ?? для значений по умолчанию
// 4. Протестируйте с разными наборами свойств
Чек-лист урока
- Optional properties (?) — свойства, которые могут отсутствовать
- Символ
?после имени свойства делает его необязательным - Optional свойства имеют тип
Type | undefined - Перед использованием optional свойства нужно проверять его наличие
- Оператор
?. (optional chaining)упрощает безопасный доступ - Оператор
?? (nullish coalescing)помогает задать значения по умолчанию - Используйте optional properties для данных, которые могут отсутствовать
- Optional свойства должны идти после обязательных
Тест по необязательным свойствам
8 вопросов