Как объявить nullable-тип (допускающий null) в TypeScript?
Использование union types (объединённых типов)
В TypeScript для объявления типа, который может быть null (или undefined), используется механизм union types (объединение типов). Вы просто указываете через вертикальную черту (|) все допустимые типы для данного значения. Это стандартный, типобезопасный способ.
Исправим ваш интерфейс Task:
interface Task {
id: number;
title: string;
completedAt: Date | null; // теперь может быть Date или null
assignee: string | null; // теперь может быть string или null
}
// Теперь это работает:
const task: Task = {
id: 1,
title: "Купить продукты",
completedAt: null, // ОК
assignee: null // ОК
};
// И это тоже работает:
const completedTask: Task = {
id: 2,
title: "Сделать отчёт",
completedAt: new Date("2024-01-15"), // ОК
assignee: "Иван Петров" // ОК
};
Разница между null и undefined
По умолчанию в TypeScript null и undefined — это разные типы. Если вы хотите, чтобы поле могло быть и null, и undefined (что часто бывает при работе с API), можно написать Date | null | undefined. Однако есть более элегантный способ — использовать опциональные свойства (optional properties).
Опциональные свойства (optional properties) — более идиоматичный подход
Если null означает «значения нет», то в TypeScript часто используют undefined вместе с опциональным модификатором ?. Это делает код чище и лучше отражает намерение: «это поле может отсутствовать».
interface Task {
id: number;
title: string;
completedAt?: Date; // может быть Date или undefined
assignee?: string; // может быть string или undefined
}
// Теперь мы можем не указывать эти поля вообще:
const task: Task = {
id: 1,
title: "Купить продукты"
// completedAt и assignee отсутствуют - это undefined
};
// Или явно присвоить undefined:
const anotherTask: Task = {
id: 2,
title: "Позвонить клиенту",
completedAt: undefined,
assignee: undefined
};
Когда использовать null, а когда undefined?
undefined+ опциональное поле (?) — используйте, когда поле может просто отсутствовать. Это стандарт для TypeScript/JavaScript.null— используйте, когда нужно явно указать «значение было сброшено/очищено». Например, если задача была выполнена, а потом статус сбросили:completedAt: null.Date | null— если вы работаете с API на бэкенде (например, C# или Java), которые часто присылают именноnull, а не отсутствующее поле.
Настройка tsconfig.json (strictNullChecks)
Убедитесь, что в вашем tsconfig.json включена опция strictNullChecks (она входит в состав strict: true). Без неё null и undefined будут совместимы с любым типом, что сводит на нет всю защиту:
{
"compilerOptions": {
"strict": true, // включает strictNullChecks и другие полезные проверки
// или явно:
"strictNullChecks": true
}
}
С strictNullChecks: true (рекомендуется) TypeScript будет жёстко следить, чтобы вы не использовали null/undefined там, где они не разрешены явно. Это предотвращает множество багов в рантайме.
Проверка на null перед использованием
Когда вы получаете значение nullable-типа, TypeScript заставит вас проверить его перед использованием:
function formatTaskDate(task: Task): string {
// Ошибка! Object is possibly 'null' или 'undefined'
// return task.completedAt.toISOString();
// Правильно: проверяем
if (task.completedAt) {
return task.completedAt.toISOString();
}
return "Не выполнено";
}
// Или с помощью optional chaining (?.)
function getAssignee(task: Task): string {
return task.assignee?.toUpperCase() ?? "Нет исполнителя";
}
<<Альтернативный подход: отдельные типы для разных состояний
Если в вашем проекте много логики, зависящей от наличия/отсутствия значения, можно использовать discriminated unions (размеченные объединения). Это делает состояния явными и улучшает читаемость кода:
// Явно описываем состояния задачи
type PendingTask = {
status: 'pending';
title: string;
// completedAt отсутствует
};
type CompletedTask = {
status: 'completed';
title: string;
completedAt: Date;
assignee: string;
};
type Task = PendingTask | CompletedTask;
// Теперь TypeScript сам подскажет, какие поля доступны:
function processTask(task: Task) {
if (task.status === 'pending') {
// Здесь доступны только поля PendingTask
console.log(`Задача "${task.title}" ожидает выполнения`);
// task.completedAt - ошибка компиляции!
} else {
// Здесь доступны поля CompletedTask
console.log(`Выполнено: ${task.completedAt.toISOString()}`);
}
}
Этот подход особенно полезен, когда у вас сложная бизнес-логика и много различных состояний (например, заказ: новый, оплачен, отправлен, доставлен). Он делает код самодокументируемым и исключает возможность случайного использования несуществующего поля.
Минус: больше кода при объявлении типов; избыточен для простых случаев с одним-двумя nullable-полями.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги