Type Narrowing — это процесс, при котором TypeScript **сужает** широкий тип до более конкретного в определённом блоке кода на основании проверок. TypeScript анализирует код и понимает, что внутри if (typeof x === 'string') переменная x гарантированно является строкой.
function processValue(value: string | number | boolean) {
if (typeof value === 'string') {
// value: string — TypeScript сузил тип
return value.toUpperCase()
}
if (typeof value === 'number') {
// value: number
return value.toFixed(2)
}
// value: boolean — единственный оставшийся вариант
return value ? 'да' : 'нет'
}instanceof работает для классов:
function formatError(error: Error | string): string {
if (error instanceof Error) {
// error: Error — TypeScript знает что это экземпляр Error
return `Ошибка: ${error.message}`
}
// error: string
return error
}
class ValidationError extends Error {
constructor(public field: string, message: string) {
super(message)
}
}
function handle(err: Error) {
if (err instanceof ValidationError) {
// err: ValidationError — можно обращаться к err.field
console.log(`Поле: ${err.field}, Сообщение: ${err.message}`)
}
}Оператор in проверяет наличие свойства в объекте:
interface Cat { meow(): void }
interface Dog { bark(): void }
function makeSound(animal: Cat | Dog) {
if ('meow' in animal) {
// animal: Cat
animal.meow()
} else {
// animal: Dog
animal.bark()
}
}Сравнение с конкретным значением:
function handleResponse(status: 'success' | 'error' | 'loading') {
if (status === 'success') {
// status: 'success'
return 'Успешно!'
}
if (status === 'error') {
// status: 'error'
return 'Ошибка!'
}
// status: 'loading'
return 'Загрузка...'
}Проверка на falsy-значения:
function printLength(str: string | null | undefined) {
if (str) {
// str: string — null и undefined отфильтрованы
// Но! '' (пустая строка) тоже отфильтруется — это может быть нежелательно
console.log(str.length)
}
}
// Более точная проверка:
function printLength(str: string | null | undefined) {
if (str != null) {
// str: string — только null и undefined отфильтрованы
console.log(str.length) // Работает даже для пустой строки
}
}Паттерн гарантированной обработки всех вариантов:
type Status = 'active' | 'inactive' | 'pending'
function describe(status: Status): string {
switch (status) {
case 'active': return 'Активен'
case 'inactive': return 'Неактивен'
case 'pending': return 'Ожидает'
default:
// Если добавить 'suspended' в Status — здесь будет ошибка TS:
// Type 'suspended' is not assignable to type 'never'
const _exhaustive: never = status
throw new Error(`Необработанный статус: ${_exhaustive}`)
}
}Пользовательские функции-охранники:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function processValues(values: unknown[]) {
values.filter(isString).forEach(str => {
// str: string — TypeScript знает тип после filter с type predicate
console.log(str.toUpperCase())
})
}Все виды narrowing в JavaScript: typeof, instanceof, in, equality, truthiness
// В TS: narrowing — TypeScript автоматически сужает тип в ветках
// В JS: те же проверки, но без compile-time гарантий
// === typeof narrowing ===
function processValue(value) {
if (typeof value === 'string') {
return `Строка: "${value.toUpperCase()}"`
}
if (typeof value === 'number') {
return `Число: ${value.toFixed(2)}`
}
if (typeof value === 'boolean') {
return `Булево: ${value ? 'true' : 'false'}`
}
if (Array.isArray(value)) {
return `Массив [${value.length}]: ${value.join(', ')}`
}
return `Объект: ${JSON.stringify(value)}`
}
console.log('=== typeof narrowing ===')
console.log(processValue('hello')) // 'Строка: "HELLO"'
console.log(processValue(3.14)) // 'Число: 3.14'
console.log(processValue(true)) // 'Булево: true'
console.log(processValue([1, 2, 3])) // 'Массив [3]: 1, 2, 3'
console.log(processValue({ x: 1 })) // 'Объект: {"x":1}'
// === instanceof narrowing ===
console.log('\n=== instanceof narrowing ===')
class NetworkError extends Error {
constructor(message, status) {
super(message)
this.name = 'NetworkError'
this.status = status
}
}
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError'
this.field = field
}
}
function handleError(error) {
if (error instanceof NetworkError) {
return `Сеть (код ${error.status}): ${error.message}`
}
if (error instanceof ValidationError) {
return `Валидация поля '${error.field}': ${error.message}`
}
if (error instanceof Error) {
return `Ошибка: ${error.message}`
}
return `Неизвестно: ${error}`
}
console.log(handleError(new NetworkError('Timeout', 504)))
console.log(handleError(new ValidationError('Слишком короткое', 'password')))
console.log(handleError(new Error('Что-то пошло не так')))
// === in operator narrowing ===
console.log('\n=== in operator narrowing ===')
function describeAnimal(animal) {
if ('meow' in animal) {
animal.meow() // В TS: animal: Cat
} else if ('bark' in animal) {
animal.bark() // В TS: animal: Dog
} else if ('chirp' in animal) {
animal.chirp() // В TS: animal: Bird
}
}
const cat = { meow: () => console.log('Мяу!'), name: 'Мурка' }
const dog = { bark: () => console.log('Гав!'), name: 'Шарик' }
const bird = { chirp: () => console.log('Чирик!'), name: 'Кеша' }
describeAnimal(cat)
describeAnimal(dog)
describeAnimal(bird)
// === Exhaustive check паттерн ===
console.log('\n=== Exhaustive check ===')
function getStatusColor(status) {
switch (status) {
case 'success': return '#22c55e' // зелёный
case 'error': return '#ef4444' // красный
case 'warning': return '#f59e0b' // жёлтый
case 'info': return '#3b82f6' // синий
default:
throw new Error(`Необработанный статус: ${status}`)
}
}
['success', 'error', 'warning', 'info'].forEach(status => {
console.log(`${status}: ${getStatusColor(status)}`)
})Type Narrowing — это процесс, при котором TypeScript **сужает** широкий тип до более конкретного в определённом блоке кода на основании проверок. TypeScript анализирует код и понимает, что внутри if (typeof x === 'string') переменная x гарантированно является строкой.
function processValue(value: string | number | boolean) {
if (typeof value === 'string') {
// value: string — TypeScript сузил тип
return value.toUpperCase()
}
if (typeof value === 'number') {
// value: number
return value.toFixed(2)
}
// value: boolean — единственный оставшийся вариант
return value ? 'да' : 'нет'
}instanceof работает для классов:
function formatError(error: Error | string): string {
if (error instanceof Error) {
// error: Error — TypeScript знает что это экземпляр Error
return `Ошибка: ${error.message}`
}
// error: string
return error
}
class ValidationError extends Error {
constructor(public field: string, message: string) {
super(message)
}
}
function handle(err: Error) {
if (err instanceof ValidationError) {
// err: ValidationError — можно обращаться к err.field
console.log(`Поле: ${err.field}, Сообщение: ${err.message}`)
}
}Оператор in проверяет наличие свойства в объекте:
interface Cat { meow(): void }
interface Dog { bark(): void }
function makeSound(animal: Cat | Dog) {
if ('meow' in animal) {
// animal: Cat
animal.meow()
} else {
// animal: Dog
animal.bark()
}
}Сравнение с конкретным значением:
function handleResponse(status: 'success' | 'error' | 'loading') {
if (status === 'success') {
// status: 'success'
return 'Успешно!'
}
if (status === 'error') {
// status: 'error'
return 'Ошибка!'
}
// status: 'loading'
return 'Загрузка...'
}Проверка на falsy-значения:
function printLength(str: string | null | undefined) {
if (str) {
// str: string — null и undefined отфильтрованы
// Но! '' (пустая строка) тоже отфильтруется — это может быть нежелательно
console.log(str.length)
}
}
// Более точная проверка:
function printLength(str: string | null | undefined) {
if (str != null) {
// str: string — только null и undefined отфильтрованы
console.log(str.length) // Работает даже для пустой строки
}
}Паттерн гарантированной обработки всех вариантов:
type Status = 'active' | 'inactive' | 'pending'
function describe(status: Status): string {
switch (status) {
case 'active': return 'Активен'
case 'inactive': return 'Неактивен'
case 'pending': return 'Ожидает'
default:
// Если добавить 'suspended' в Status — здесь будет ошибка TS:
// Type 'suspended' is not assignable to type 'never'
const _exhaustive: never = status
throw new Error(`Необработанный статус: ${_exhaustive}`)
}
}Пользовательские функции-охранники:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function processValues(values: unknown[]) {
values.filter(isString).forEach(str => {
// str: string — TypeScript знает тип после filter с type predicate
console.log(str.toUpperCase())
})
}Все виды narrowing в JavaScript: typeof, instanceof, in, equality, truthiness
// В TS: narrowing — TypeScript автоматически сужает тип в ветках
// В JS: те же проверки, но без compile-time гарантий
// === typeof narrowing ===
function processValue(value) {
if (typeof value === 'string') {
return `Строка: "${value.toUpperCase()}"`
}
if (typeof value === 'number') {
return `Число: ${value.toFixed(2)}`
}
if (typeof value === 'boolean') {
return `Булево: ${value ? 'true' : 'false'}`
}
if (Array.isArray(value)) {
return `Массив [${value.length}]: ${value.join(', ')}`
}
return `Объект: ${JSON.stringify(value)}`
}
console.log('=== typeof narrowing ===')
console.log(processValue('hello')) // 'Строка: "HELLO"'
console.log(processValue(3.14)) // 'Число: 3.14'
console.log(processValue(true)) // 'Булево: true'
console.log(processValue([1, 2, 3])) // 'Массив [3]: 1, 2, 3'
console.log(processValue({ x: 1 })) // 'Объект: {"x":1}'
// === instanceof narrowing ===
console.log('\n=== instanceof narrowing ===')
class NetworkError extends Error {
constructor(message, status) {
super(message)
this.name = 'NetworkError'
this.status = status
}
}
class ValidationError extends Error {
constructor(message, field) {
super(message)
this.name = 'ValidationError'
this.field = field
}
}
function handleError(error) {
if (error instanceof NetworkError) {
return `Сеть (код ${error.status}): ${error.message}`
}
if (error instanceof ValidationError) {
return `Валидация поля '${error.field}': ${error.message}`
}
if (error instanceof Error) {
return `Ошибка: ${error.message}`
}
return `Неизвестно: ${error}`
}
console.log(handleError(new NetworkError('Timeout', 504)))
console.log(handleError(new ValidationError('Слишком короткое', 'password')))
console.log(handleError(new Error('Что-то пошло не так')))
// === in operator narrowing ===
console.log('\n=== in operator narrowing ===')
function describeAnimal(animal) {
if ('meow' in animal) {
animal.meow() // В TS: animal: Cat
} else if ('bark' in animal) {
animal.bark() // В TS: animal: Dog
} else if ('chirp' in animal) {
animal.chirp() // В TS: animal: Bird
}
}
const cat = { meow: () => console.log('Мяу!'), name: 'Мурка' }
const dog = { bark: () => console.log('Гав!'), name: 'Шарик' }
const bird = { chirp: () => console.log('Чирик!'), name: 'Кеша' }
describeAnimal(cat)
describeAnimal(dog)
describeAnimal(bird)
// === Exhaustive check паттерн ===
console.log('\n=== Exhaustive check ===')
function getStatusColor(status) {
switch (status) {
case 'success': return '#22c55e' // зелёный
case 'error': return '#ef4444' // красный
case 'warning': return '#f59e0b' // жёлтый
case 'info': return '#3b82f6' // синий
default:
throw new Error(`Необработанный статус: ${status}`)
}
}
['success', 'error', 'warning', 'info'].forEach(status => {
console.log(`${status}: ${getStatusColor(status)}`)
})Реализуй функцию `describe(value)` которая использует все виды narrowing: если строка — "Строка длиной N"; если число и > 0 — "Положительное число N"; если число и <= 0 — "Неположительное число N"; если null — "null"; если undefined — "undefined"; если массив — "Массив из N элементов"; если объект — "Объект с ключами: k1, k2, ...".
Порядок проверок критически важен: сначала value === null, затем value === undefined, затем Array.isArray(value), затем typeof value === "string", затем typeof value === "number" (с проверкой > 0), последним typeof value === "object". Для объекта: Object.keys(value).join(", ").
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке