TypeScript **сужает** (narrows) тип переменной после проверки. В разных ветках кода переменная имеет разные типы:
function process(value: string | number) {
// Здесь value: string | number
if (typeof value === 'string') {
// Здесь value: string — TypeScript знает это!
return value.toUpperCase()
}
// Здесь value: number
return value.toFixed(2)
}Работает для примитивов: string, number, boolean, bigint, symbol, function, undefined. Но typeof null === 'object' — исторический баг JavaScript!
function stringify(value: unknown): string {
if (typeof value === 'string') return value
if (typeof value === 'number') return value.toString()
if (typeof value === 'boolean') return value ? 'true' : 'false'
if (typeof value === 'undefined') return 'undefined'
return JSON.stringify(value)
}Для классов и объектов встроенных типов:
function formatDate(value: Date | string): string {
if (value instanceof Date) {
return value.toLocaleDateString() // value: Date
}
return new Date(value).toLocaleDateString() // value: string
}
function handleError(err: unknown): string {
if (err instanceof Error) {
return err.message // err: Error — есть .message
}
return String(err)
}Проверяет наличие свойства в объекте:
type Cat = { name: string; meow(): void }
type Dog = { name: string; bark(): void }
function makeSound(animal: Cat | Dog) {
if ('meow' in animal) {
animal.meow() // animal: Cat
} else {
animal.bark() // animal: Dog
}
}Функция, которая возвращает value is Type — сообщает TypeScript о сужении:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as any).name === 'string'
)
}
// Использование:
function greet(value: unknown) {
if (isUser(value)) {
console.log(value.name) // TypeScript знает: value.name: string
}
}Самый надёжный способ — поле-тег:
type Result<T> =
| { success: true; value: T }
| { success: false; error: string }
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.value) // result: { success: true, value: T }
} else {
console.error(result.error) // result: { success: false, error: string }
}
}function processName(name: string | null | undefined) {
if (name) {
// name: string — null и undefined исключены
return name.toUpperCase()
}
return 'Аноним'
}Осторожно: пустая строка '' тоже falsy!
| Сценарий | Guard |
|---|---|
| Примитивы (string/number/boolean) | typeof |
| Классы, Date, Error, Array | instanceof |
| Объекты с разными полями | in |
| Сложная проверка структуры | custom type guard |
| Объекты с общим полем-тегом | discriminated union |
| null/undefined | truthiness / === null |
Набор type guard функций и parseUserInput для обработки разных форматов входных данных
// Type guard функции — в TypeScript возвращали бы val is string и т.д.
function isString(val) {
return typeof val === 'string'
}
function isNumber(val) {
return typeof val === 'number' && !isNaN(val)
}
function isBoolean(val) {
return typeof val === 'boolean'
}
function isArray(val) {
return Array.isArray(val)
}
function isObject(val) {
return typeof val === 'object' && val !== null && !Array.isArray(val)
}
function isNullish(val) {
return val === null || val === undefined
}
function isFunction(val) {
return typeof val === 'function'
}
function isDate(val) {
return val instanceof Date && !isNaN(val.getTime())
}
// Кастомный guard для User-подобного объекта
function isUserLike(val) {
return isObject(val) && isString(val.name)
}
// Функция parseUserInput использует все guards
function parseUserInput(input) {
if (isNullish(input)) {
return { type: 'empty', value: null, display: '(пусто)' }
}
if (isBoolean(input)) {
return { type: 'boolean', value: input, display: input ? 'да' : 'нет' }
}
if (isNumber(input)) {
return {
type: 'number',
value: input,
display: input.toLocaleString('ru-RU'),
}
}
if (isString(input)) {
// Попробуем распарсить как число
const parsed = Number(input)
if (!isNaN(parsed) && input.trim() !== '') {
return { type: 'number-string', value: parsed, display: String(parsed) }
}
// Попробуем распарсить как дату
const date = new Date(input)
if (!isNaN(date.getTime()) && input.includes('-')) {
return {
type: 'date-string',
value: date,
display: date.toLocaleDateString('ru-RU'),
}
}
return { type: 'string', value: input, display: `"${input}"` }
}
if (isDate(input)) {
return {
type: 'date',
value: input,
display: input.toLocaleDateString('ru-RU'),
}
}
if (isArray(input)) {
return {
type: 'array',
value: input,
display: `[${input.length} элем.]`,
}
}
if (isUserLike(input)) {
return {
type: 'user',
value: input,
display: `User: ${input.name}${input.age ? `, ${input.age} лет` : ''}`,
}
}
if (isObject(input)) {
return {
type: 'object',
value: input,
display: `{keys: ${Object.keys(input).join(', ')}}`,
}
}
if (isFunction(input)) {
return { type: 'function', value: input, display: `fn(${input.name})` }
}
return { type: 'unknown', value: input, display: String(input) }
}
// --- Демонстрация ---
const inputs = [
null,
undefined,
true,
false,
42,
3.14,
'hello',
'123',
'2024-01-15',
new Date(),
[1, 2, 3],
{ name: 'Алексей', age: 30 },
{ x: 1, y: 2 },
Math.max,
]
console.log('=== parseUserInput для разных значений ===')
inputs.forEach(input => {
const result = parseUserInput(input)
console.log(`[${result.type}] ${result.display}`)
})TypeScript **сужает** (narrows) тип переменной после проверки. В разных ветках кода переменная имеет разные типы:
function process(value: string | number) {
// Здесь value: string | number
if (typeof value === 'string') {
// Здесь value: string — TypeScript знает это!
return value.toUpperCase()
}
// Здесь value: number
return value.toFixed(2)
}Работает для примитивов: string, number, boolean, bigint, symbol, function, undefined. Но typeof null === 'object' — исторический баг JavaScript!
function stringify(value: unknown): string {
if (typeof value === 'string') return value
if (typeof value === 'number') return value.toString()
if (typeof value === 'boolean') return value ? 'true' : 'false'
if (typeof value === 'undefined') return 'undefined'
return JSON.stringify(value)
}Для классов и объектов встроенных типов:
function formatDate(value: Date | string): string {
if (value instanceof Date) {
return value.toLocaleDateString() // value: Date
}
return new Date(value).toLocaleDateString() // value: string
}
function handleError(err: unknown): string {
if (err instanceof Error) {
return err.message // err: Error — есть .message
}
return String(err)
}Проверяет наличие свойства в объекте:
type Cat = { name: string; meow(): void }
type Dog = { name: string; bark(): void }
function makeSound(animal: Cat | Dog) {
if ('meow' in animal) {
animal.meow() // animal: Cat
} else {
animal.bark() // animal: Dog
}
}Функция, которая возвращает value is Type — сообщает TypeScript о сужении:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as any).name === 'string'
)
}
// Использование:
function greet(value: unknown) {
if (isUser(value)) {
console.log(value.name) // TypeScript знает: value.name: string
}
}Самый надёжный способ — поле-тег:
type Result<T> =
| { success: true; value: T }
| { success: false; error: string }
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.value) // result: { success: true, value: T }
} else {
console.error(result.error) // result: { success: false, error: string }
}
}function processName(name: string | null | undefined) {
if (name) {
// name: string — null и undefined исключены
return name.toUpperCase()
}
return 'Аноним'
}Осторожно: пустая строка '' тоже falsy!
| Сценарий | Guard |
|---|---|
| Примитивы (string/number/boolean) | typeof |
| Классы, Date, Error, Array | instanceof |
| Объекты с разными полями | in |
| Сложная проверка структуры | custom type guard |
| Объекты с общим полем-тегом | discriminated union |
| null/undefined | truthiness / === null |
Набор type guard функций и parseUserInput для обработки разных форматов входных данных
// Type guard функции — в TypeScript возвращали бы val is string и т.д.
function isString(val) {
return typeof val === 'string'
}
function isNumber(val) {
return typeof val === 'number' && !isNaN(val)
}
function isBoolean(val) {
return typeof val === 'boolean'
}
function isArray(val) {
return Array.isArray(val)
}
function isObject(val) {
return typeof val === 'object' && val !== null && !Array.isArray(val)
}
function isNullish(val) {
return val === null || val === undefined
}
function isFunction(val) {
return typeof val === 'function'
}
function isDate(val) {
return val instanceof Date && !isNaN(val.getTime())
}
// Кастомный guard для User-подобного объекта
function isUserLike(val) {
return isObject(val) && isString(val.name)
}
// Функция parseUserInput использует все guards
function parseUserInput(input) {
if (isNullish(input)) {
return { type: 'empty', value: null, display: '(пусто)' }
}
if (isBoolean(input)) {
return { type: 'boolean', value: input, display: input ? 'да' : 'нет' }
}
if (isNumber(input)) {
return {
type: 'number',
value: input,
display: input.toLocaleString('ru-RU'),
}
}
if (isString(input)) {
// Попробуем распарсить как число
const parsed = Number(input)
if (!isNaN(parsed) && input.trim() !== '') {
return { type: 'number-string', value: parsed, display: String(parsed) }
}
// Попробуем распарсить как дату
const date = new Date(input)
if (!isNaN(date.getTime()) && input.includes('-')) {
return {
type: 'date-string',
value: date,
display: date.toLocaleDateString('ru-RU'),
}
}
return { type: 'string', value: input, display: `"${input}"` }
}
if (isDate(input)) {
return {
type: 'date',
value: input,
display: input.toLocaleDateString('ru-RU'),
}
}
if (isArray(input)) {
return {
type: 'array',
value: input,
display: `[${input.length} элем.]`,
}
}
if (isUserLike(input)) {
return {
type: 'user',
value: input,
display: `User: ${input.name}${input.age ? `, ${input.age} лет` : ''}`,
}
}
if (isObject(input)) {
return {
type: 'object',
value: input,
display: `{keys: ${Object.keys(input).join(', ')}}`,
}
}
if (isFunction(input)) {
return { type: 'function', value: input, display: `fn(${input.name})` }
}
return { type: 'unknown', value: input, display: String(input) }
}
// --- Демонстрация ---
const inputs = [
null,
undefined,
true,
false,
42,
3.14,
'hello',
'123',
'2024-01-15',
new Date(),
[1, 2, 3],
{ name: 'Алексей', age: 30 },
{ x: 1, y: 2 },
Math.max,
]
console.log('=== parseUserInput для разных значений ===')
inputs.forEach(input => {
const result = parseUserInput(input)
console.log(`[${result.type}] ${result.display}`)
})Реализуй функцию `safeParse(input)` — принимает любое значение и возвращает `{type, value}` где type — строка: "string", "number", "boolean", "array", "object", "null", "undefined". Реализуй `assertString(val)` и `assertNumber(val)` — бросают `TypeError` с сообщением "Expected string, got <type>" если тип не совпадает.
В safeParse: сначала проверь null (val === null), потом undefined (val === undefined), потом Array.isArray(val), потом typeof. В assertString/assertNumber: if (typeof val !== "string") throw new TypeError("Expected string, got " + typeof val).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке