function isString(value: unknown): boolean {
return typeof value === 'string'
}
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript всё ещё считает value: string | number
value.toUpperCase() // Ошибка TS! TypeScript не «помнит» что мы проверили
}
}Проблема: вынос проверки в функцию «стирает» информацию о типе для TypeScript.
// Возвращаемый тип — predicate: «если функция вернула true, то value — это string»
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function processValue(value: string | number) {
if (isString(value)) {
value.toUpperCase() // OK! TypeScript знает: value — string
} else {
value.toFixed(2) // OK! TypeScript знает: value — number
}
}interface Cat { meow(): void; purr(): void }
interface Dog { bark(): void; fetch(): void }
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal
}
function makeNoise(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow() // animal is Cat — TS знает
animal.purr()
} else {
animal.bark() // animal is Dog
}
}// Если функция не бросает — значит value является T
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new TypeError(`Ожидалась строка, получен ${typeof value}`)
}
}
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
if (value == null) {
throw new Error('Значение не должно быть null или undefined')
}
}
const maybeString: unknown = 'hello'
assertIsString(maybeString)
maybeString.toUpperCase() // OK — TypeScript знает что это stringБез type predicate TypeScript не может сузить тип после filter:
const values: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c']
// Без predicate — тип элементов всё ещё string | null | undefined
const v1 = values.filter(Boolean) // (string | null | undefined)[]
// С predicate — TypeScript знает что отфильтрованы null/undefined
function isDefined<T>(value: T | null | undefined): value is T {
return value != null
}
const v2 = values.filter(isDefined) // string[] — точный тип!interface User { id: number; name: string; role: 'user' }
interface Admin { id: number; name: string; role: 'admin'; permissions: string[] }
type Person = User | Admin
function isAdmin(person: Person): person is Admin {
return person.role === 'admin'
}
// Комбинирование
function hasPermission(person: Person, perm: string): boolean {
return isAdmin(person) && person.permissions.includes(perm)
}
// Массив — оставить только Admin
const people: Person[] = [...]
const admins = people.filter(isAdmin) // Admin[] — тип сужен!Type guard функции для runtime проверки типов: isString, isNumber, isDefined, isUser — паттерны для безопасной работы с unknown данными
// TypeScript type predicates — это обычные функции-предикаты,
// но с аннотацией которая сообщает TypeScript о сужении типа.
// В JS используем те же предикатные функции для runtime-безопасности.
// Базовые предикаты (аналог: value is string)
const isString = (v) => typeof v === 'string'
const isNumber = (v) => typeof v === 'number' && !isNaN(v)
const isBoolean = (v) => typeof v === 'boolean'
const isArray = (v) => Array.isArray(v)
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
const isDefined = (v) => v != null // исключает null и undefined
// Составной предикат: создаём type guard для объектов
function hasShape(value, shape) {
if (!isObject(value)) return false
for (const [key, validator] of Object.entries(shape)) {
if (typeof validator === 'string') {
if (typeof value[key] !== validator) return false
} else if (typeof validator === 'function') {
if (!validator(value[key])) return false
}
}
return true
}
// Конкретные type guards для наших типов
const isUser = (v) => hasShape(v, {
id: 'number',
name: 'string',
email: isString,
})
const isAdmin = (v) => isUser(v) && v.role === 'admin'
const isProduct = (v) => hasShape(v, {
id: 'number',
name: 'string',
price: isNumber,
})
// assert — бросает если условие не выполнено
function assert(condition, message) {
if (!condition) throw new TypeError(message)
}
function assertDefined(value, name = 'value') {
assert(isDefined(value), `${name} не должен быть null/undefined`)
return value
}
function assertUser(value) {
assert(isUser(value), `Ожидается объект User: ${JSON.stringify(value)}`)
return value
}
// filter с предикатом — аналог array.filter(isDefined) в TS
function filterDefined(arr) {
return arr.filter(isDefined)
}
function filterByPredicate(arr, predicate) {
return arr.filter(predicate)
}
// --- Демонстрация ---
console.log('=== Базовые предикаты ===')
const values = [1, 'hello', null, true, undefined, 42, '', false, {}, []]
console.log('isString:', values.filter(isString))
console.log('isNumber:', values.filter(isNumber))
console.log('isDefined:', filterDefined(values))
console.log('\n=== hasShape type guard ===')
const data = [
{ id: 1, name: 'Алексей', email: 'alex@mail.ru' },
{ id: 2, name: 'Ольга', email: 'olga@mail.ru', role: 'admin' },
{ id: 'bad', name: 'Иван' }, // невалидный
'just a string',
null,
{ id: 3, name: 'Мария', email: 'maria@mail.ru' },
]
const users = filterByPredicate(data, isUser)
console.log('Валидных пользователей:', users.length) // 3
users.forEach(u => console.log(` - ${u.name} (${u.email})`))
console.log('\n=== Array.filter с type guard ===')
const maybeUsers = [
{ id: 1, name: 'Alice', email: 'a@b.com' },
null,
{ id: 2, name: 'Bob', email: 'b@c.com' },
undefined,
{ id: 'bad' },
]
const validUsers = maybeUsers.filter(isUser)
console.log('Прошли фильтр:', validUsers.length)
// В TypeScript это дало бы тип User[] а не (User | null | undefined)[]
console.log('\n=== assert (не бросает — гарантирует тип) ===')
function processUser(data) {
assertDefined(data, 'data')
assertUser(data)
console.log(`Обработка: ${data.name} (${data.email})`)
}
processUser({ id: 1, name: 'Иван', email: 'ivan@mail.ru' })
try {
processUser(null)
} catch (e) {
console.log('Ошибка null:', e.message)
}
try {
processUser({ name: 'без id и email' })
} catch (e) {
console.log('Ошибка невалидного:', e.message)
}function isString(value: unknown): boolean {
return typeof value === 'string'
}
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript всё ещё считает value: string | number
value.toUpperCase() // Ошибка TS! TypeScript не «помнит» что мы проверили
}
}Проблема: вынос проверки в функцию «стирает» информацию о типе для TypeScript.
// Возвращаемый тип — predicate: «если функция вернула true, то value — это string»
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function processValue(value: string | number) {
if (isString(value)) {
value.toUpperCase() // OK! TypeScript знает: value — string
} else {
value.toFixed(2) // OK! TypeScript знает: value — number
}
}interface Cat { meow(): void; purr(): void }
interface Dog { bark(): void; fetch(): void }
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal
}
function makeNoise(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow() // animal is Cat — TS знает
animal.purr()
} else {
animal.bark() // animal is Dog
}
}// Если функция не бросает — значит value является T
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new TypeError(`Ожидалась строка, получен ${typeof value}`)
}
}
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
if (value == null) {
throw new Error('Значение не должно быть null или undefined')
}
}
const maybeString: unknown = 'hello'
assertIsString(maybeString)
maybeString.toUpperCase() // OK — TypeScript знает что это stringБез type predicate TypeScript не может сузить тип после filter:
const values: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c']
// Без predicate — тип элементов всё ещё string | null | undefined
const v1 = values.filter(Boolean) // (string | null | undefined)[]
// С predicate — TypeScript знает что отфильтрованы null/undefined
function isDefined<T>(value: T | null | undefined): value is T {
return value != null
}
const v2 = values.filter(isDefined) // string[] — точный тип!interface User { id: number; name: string; role: 'user' }
interface Admin { id: number; name: string; role: 'admin'; permissions: string[] }
type Person = User | Admin
function isAdmin(person: Person): person is Admin {
return person.role === 'admin'
}
// Комбинирование
function hasPermission(person: Person, perm: string): boolean {
return isAdmin(person) && person.permissions.includes(perm)
}
// Массив — оставить только Admin
const people: Person[] = [...]
const admins = people.filter(isAdmin) // Admin[] — тип сужен!Type guard функции для runtime проверки типов: isString, isNumber, isDefined, isUser — паттерны для безопасной работы с unknown данными
// TypeScript type predicates — это обычные функции-предикаты,
// но с аннотацией которая сообщает TypeScript о сужении типа.
// В JS используем те же предикатные функции для runtime-безопасности.
// Базовые предикаты (аналог: value is string)
const isString = (v) => typeof v === 'string'
const isNumber = (v) => typeof v === 'number' && !isNaN(v)
const isBoolean = (v) => typeof v === 'boolean'
const isArray = (v) => Array.isArray(v)
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
const isDefined = (v) => v != null // исключает null и undefined
// Составной предикат: создаём type guard для объектов
function hasShape(value, shape) {
if (!isObject(value)) return false
for (const [key, validator] of Object.entries(shape)) {
if (typeof validator === 'string') {
if (typeof value[key] !== validator) return false
} else if (typeof validator === 'function') {
if (!validator(value[key])) return false
}
}
return true
}
// Конкретные type guards для наших типов
const isUser = (v) => hasShape(v, {
id: 'number',
name: 'string',
email: isString,
})
const isAdmin = (v) => isUser(v) && v.role === 'admin'
const isProduct = (v) => hasShape(v, {
id: 'number',
name: 'string',
price: isNumber,
})
// assert — бросает если условие не выполнено
function assert(condition, message) {
if (!condition) throw new TypeError(message)
}
function assertDefined(value, name = 'value') {
assert(isDefined(value), `${name} не должен быть null/undefined`)
return value
}
function assertUser(value) {
assert(isUser(value), `Ожидается объект User: ${JSON.stringify(value)}`)
return value
}
// filter с предикатом — аналог array.filter(isDefined) в TS
function filterDefined(arr) {
return arr.filter(isDefined)
}
function filterByPredicate(arr, predicate) {
return arr.filter(predicate)
}
// --- Демонстрация ---
console.log('=== Базовые предикаты ===')
const values = [1, 'hello', null, true, undefined, 42, '', false, {}, []]
console.log('isString:', values.filter(isString))
console.log('isNumber:', values.filter(isNumber))
console.log('isDefined:', filterDefined(values))
console.log('\n=== hasShape type guard ===')
const data = [
{ id: 1, name: 'Алексей', email: 'alex@mail.ru' },
{ id: 2, name: 'Ольга', email: 'olga@mail.ru', role: 'admin' },
{ id: 'bad', name: 'Иван' }, // невалидный
'just a string',
null,
{ id: 3, name: 'Мария', email: 'maria@mail.ru' },
]
const users = filterByPredicate(data, isUser)
console.log('Валидных пользователей:', users.length) // 3
users.forEach(u => console.log(` - ${u.name} (${u.email})`))
console.log('\n=== Array.filter с type guard ===')
const maybeUsers = [
{ id: 1, name: 'Alice', email: 'a@b.com' },
null,
{ id: 2, name: 'Bob', email: 'b@c.com' },
undefined,
{ id: 'bad' },
]
const validUsers = maybeUsers.filter(isUser)
console.log('Прошли фильтр:', validUsers.length)
// В TypeScript это дало бы тип User[] а не (User | null | undefined)[]
console.log('\n=== assert (не бросает — гарантирует тип) ===')
function processUser(data) {
assertDefined(data, 'data')
assertUser(data)
console.log(`Обработка: ${data.name} (${data.email})`)
}
processUser({ id: 1, name: 'Иван', email: 'ivan@mail.ru' })
try {
processUser(null)
} catch (e) {
console.log('Ошибка null:', e.message)
}
try {
processUser({ name: 'без id и email' })
} catch (e) {
console.log('Ошибка невалидного:', e.message)
}Реализуй функцию `isValidDate(value)` — возвращает true если value является объектом Date с валидной датой (не NaN). Реализуй функцию `isPositiveNumber(value)` — возвращает true если value — число, не NaN, и больше нуля. Реализуй функцию `isNonEmptyArray(value)` — возвращает true если value — непустой массив. Реализуй функцию `processItems(items)` — принимает массив смешанных значений, фильтрует через isPositiveNumber и возвращает их сумму.
isValidDate: return value instanceof Date && !isNaN(value.getTime()). isPositiveNumber: return typeof value === "number" && !isNaN(value) && value > 0. isNonEmptyArray: return Array.isArray(value) && value.length > 0. processItems: return items.filter(isPositiveNumber).reduce((sum, n) => sum + n, 0).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке