Extract, Exclude и NonNullable — стандартные утилиты для работы с union types. Все они реализованы через дистрибутивные conditional types.
Exclude<T, U> убирает из T все типы, совместимые с U:
type Status = 'pending' | 'active' | 'blocked' | 'deleted'
type ActiveStatus = Exclude<Status, 'deleted' | 'blocked'>
// 'pending' | 'active'
// Реализация:
type Exclude<T, U> = T extends U ? never : T
// Для каждого члена T: если совместим с U — заменяем на never
// never в union исчезает
type NoFunctions<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K]
}Extract<T, U> оставляет из T только типы, совместимые с U:
type Events = 'click' | 'mouseover' | 'keydown' | 'keyup' | 'scroll'
type MouseEvents = Extract<Events, 'click' | 'mouseover' | `mouse${string}`>
// 'click' | 'mouseover'
type KeyEvents = Extract<Events, `key${string}`>
// 'keydown' | 'keyup'
// Реализация:
type Extract<T, U> = T extends U ? T : never
// Практика: получить общие ключи двух типов
type CommonKeys<A, B> = Extract<keyof A, keyof B>
interface User { id: number; name: string; email: string }
interface Profile { id: number; name: string; avatar: string }
type Shared = CommonKeys<User, Profile> // 'id' | 'name'type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString> // string
// Реализация:
type NonNullable<T> = T extends null | undefined ? never : T
// Или: type NonNullable<T> = T & {}
// Практика: сделать все поля объекта non-nullable
type Required<T> = { [K in keyof T]-?: NonNullable<T[K]> }// 1. Фильтрация ключей по типу значения
type KeysOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]
interface Form {
name: string
age: number
email: string
active: boolean
role: 'admin' | 'user'
}
type StringFields = KeysOfType<Form, string> // 'name' | 'email'
type NumberFields = KeysOfType<Form, number> // 'age'
type BooleanFields = KeysOfType<Form, boolean> // 'active'
// 2. Строить типы из кусков union
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
type SafeMethod = Extract<HttpMethod, 'GET' | 'HEAD' | 'OPTIONS'> // 'GET'
type MutatingMethod = Exclude<HttpMethod, 'GET'> // 'POST' | 'PUT' | 'DELETE' | 'PATCH'
// 3. Комбинация с ReturnType
function fetchUser() { return { id: 1, name: 'Алексей', deletedAt: null as Date | null } }
type FetchResult = ReturnType<typeof fetchUser>
type DefinedFetchResult = { [K in keyof FetchResult]: NonNullable<FetchResult[K]> }// PickByValue — как Pick, но по типу значения
type PickByValue<T, V> = Pick<T, KeysOfType<T, V>>
// OmitByValue — как Omit, но по типу значения
type OmitByValue<T, V> = Pick<T, Exclude<keyof T, KeysOfType<T, V>>>
interface User {
id: number
name: string
email: string
age: number
active: boolean
}
type StringProps = PickByValue<User, string> // { name: string; email: string }
type NonStrings = OmitByValue<User, string> // { id: number; age: number; active: boolean }Runtime аналоги Extract, Exclude, NonNullable: фильтрация массивов значений и ключей объектов
// TypeScript Extract/Exclude работают с типами.
// В JS реализуем аналогичные операции для значений и объектов.
// Аналог Exclude<T, U>: убрать из массива элементы, входящие в другой массив
function exclude(values, toRemove) {
const removeSet = new Set(toRemove)
return values.filter(v => !removeSet.has(v))
}
// Аналог Extract<T, U>: оставить только элементы, входящие в другой массив
function extract(values, toKeep) {
const keepSet = new Set(toKeep)
return values.filter(v => keepSet.has(v))
}
// Аналог NonNullable<T>: убрать null и undefined
function nonNullable(values) {
return values.filter(v => v != null)
}
// Аналог KeysOfType<T, V>: получить ключи объекта с определённым типом значения
function keysOfType(obj, typeCheck) {
return Object.keys(obj).filter(key => typeCheck(obj[key]))
}
// Аналог PickByValue<T, V>: взять только поля с определённым типом значения
function pickByValue(obj, typeCheck) {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => typeCheck(v))
)
}
// Аналог OmitByValue<T, V>: убрать поля с определённым типом значения
function omitByValue(obj, typeCheck) {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => !typeCheck(v))
)
}
// CommonKeys между двумя объектами (Extract<keyof A, keyof B>)
function commonKeys(objA, objB) {
const keysB = new Set(Object.keys(objB))
return Object.keys(objA).filter(k => keysB.has(k))
}
// --- Демонстрация ---
const allStatuses = ['pending', 'active', 'blocked', 'deleted', 'suspended']
console.log('=== Exclude ===')
const activeStatuses = exclude(allStatuses, ['deleted', 'blocked', 'suspended'])
console.log('Активные статусы:', activeStatuses) // ['pending', 'active']
const allEvents = ['click', 'mouseover', 'keydown', 'keyup', 'scroll', 'mouseout']
const mouseEvents = extract(allEvents, ['click', 'mouseover', 'mouseout', 'mouseenter'])
console.log('\n=== Extract ===')
console.log('Mouse события:', mouseEvents) // ['click', 'mouseover', 'mouseout']
const keyEvents = allEvents.filter(e => e.startsWith('key'))
console.log('Key события:', keyEvents) // ['keydown', 'keyup']
console.log('\n=== NonNullable ===')
const mixed = [1, null, 'hello', undefined, false, 0, '', null, 42]
const defined = nonNullable(mixed)
console.log('Без null/undefined:', defined) // [1, 'hello', false, 0, '', 42]
console.log('\n=== KeysOfType ===')
const formData = {
name: 'Алексей',
age: 30,
email: 'alex@mail.ru',
active: true,
score: 98.5,
role: 'admin',
}
const stringKeys = keysOfType(formData, v => typeof v === 'string')
const numberKeys = keysOfType(formData, v => typeof v === 'number')
const booleanKeys = keysOfType(formData, v => typeof v === 'boolean')
console.log('Строковые поля:', stringKeys) // ['name', 'email', 'role']
console.log('Числовые поля:', numberKeys) // ['age', 'score']
console.log('Булевы поля:', booleanKeys) // ['active']
console.log('\n=== PickByValue / OmitByValue ===')
const stringOnly = pickByValue(formData, v => typeof v === 'string')
console.log('Только строки:', stringOnly)
const noStrings = omitByValue(formData, v => typeof v === 'string')
console.log('Без строк:', noStrings)
console.log('\n=== CommonKeys ===')
const user = { id: 1, name: 'Алексей', email: 'a@b.ru', role: 'user' }
const profile = { id: 1, name: 'Алексей', avatar: 'img.png', bio: 'Dev' }
const shared = commonKeys(user, profile)
console.log('Общие ключи:', shared) // ['id', 'name']Extract, Exclude и NonNullable — стандартные утилиты для работы с union types. Все они реализованы через дистрибутивные conditional types.
Exclude<T, U> убирает из T все типы, совместимые с U:
type Status = 'pending' | 'active' | 'blocked' | 'deleted'
type ActiveStatus = Exclude<Status, 'deleted' | 'blocked'>
// 'pending' | 'active'
// Реализация:
type Exclude<T, U> = T extends U ? never : T
// Для каждого члена T: если совместим с U — заменяем на never
// never в union исчезает
type NoFunctions<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K]
}Extract<T, U> оставляет из T только типы, совместимые с U:
type Events = 'click' | 'mouseover' | 'keydown' | 'keyup' | 'scroll'
type MouseEvents = Extract<Events, 'click' | 'mouseover' | `mouse${string}`>
// 'click' | 'mouseover'
type KeyEvents = Extract<Events, `key${string}`>
// 'keydown' | 'keyup'
// Реализация:
type Extract<T, U> = T extends U ? T : never
// Практика: получить общие ключи двух типов
type CommonKeys<A, B> = Extract<keyof A, keyof B>
interface User { id: number; name: string; email: string }
interface Profile { id: number; name: string; avatar: string }
type Shared = CommonKeys<User, Profile> // 'id' | 'name'type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString> // string
// Реализация:
type NonNullable<T> = T extends null | undefined ? never : T
// Или: type NonNullable<T> = T & {}
// Практика: сделать все поля объекта non-nullable
type Required<T> = { [K in keyof T]-?: NonNullable<T[K]> }// 1. Фильтрация ключей по типу значения
type KeysOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]
interface Form {
name: string
age: number
email: string
active: boolean
role: 'admin' | 'user'
}
type StringFields = KeysOfType<Form, string> // 'name' | 'email'
type NumberFields = KeysOfType<Form, number> // 'age'
type BooleanFields = KeysOfType<Form, boolean> // 'active'
// 2. Строить типы из кусков union
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
type SafeMethod = Extract<HttpMethod, 'GET' | 'HEAD' | 'OPTIONS'> // 'GET'
type MutatingMethod = Exclude<HttpMethod, 'GET'> // 'POST' | 'PUT' | 'DELETE' | 'PATCH'
// 3. Комбинация с ReturnType
function fetchUser() { return { id: 1, name: 'Алексей', deletedAt: null as Date | null } }
type FetchResult = ReturnType<typeof fetchUser>
type DefinedFetchResult = { [K in keyof FetchResult]: NonNullable<FetchResult[K]> }// PickByValue — как Pick, но по типу значения
type PickByValue<T, V> = Pick<T, KeysOfType<T, V>>
// OmitByValue — как Omit, но по типу значения
type OmitByValue<T, V> = Pick<T, Exclude<keyof T, KeysOfType<T, V>>>
interface User {
id: number
name: string
email: string
age: number
active: boolean
}
type StringProps = PickByValue<User, string> // { name: string; email: string }
type NonStrings = OmitByValue<User, string> // { id: number; age: number; active: boolean }Runtime аналоги Extract, Exclude, NonNullable: фильтрация массивов значений и ключей объектов
// TypeScript Extract/Exclude работают с типами.
// В JS реализуем аналогичные операции для значений и объектов.
// Аналог Exclude<T, U>: убрать из массива элементы, входящие в другой массив
function exclude(values, toRemove) {
const removeSet = new Set(toRemove)
return values.filter(v => !removeSet.has(v))
}
// Аналог Extract<T, U>: оставить только элементы, входящие в другой массив
function extract(values, toKeep) {
const keepSet = new Set(toKeep)
return values.filter(v => keepSet.has(v))
}
// Аналог NonNullable<T>: убрать null и undefined
function nonNullable(values) {
return values.filter(v => v != null)
}
// Аналог KeysOfType<T, V>: получить ключи объекта с определённым типом значения
function keysOfType(obj, typeCheck) {
return Object.keys(obj).filter(key => typeCheck(obj[key]))
}
// Аналог PickByValue<T, V>: взять только поля с определённым типом значения
function pickByValue(obj, typeCheck) {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => typeCheck(v))
)
}
// Аналог OmitByValue<T, V>: убрать поля с определённым типом значения
function omitByValue(obj, typeCheck) {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => !typeCheck(v))
)
}
// CommonKeys между двумя объектами (Extract<keyof A, keyof B>)
function commonKeys(objA, objB) {
const keysB = new Set(Object.keys(objB))
return Object.keys(objA).filter(k => keysB.has(k))
}
// --- Демонстрация ---
const allStatuses = ['pending', 'active', 'blocked', 'deleted', 'suspended']
console.log('=== Exclude ===')
const activeStatuses = exclude(allStatuses, ['deleted', 'blocked', 'suspended'])
console.log('Активные статусы:', activeStatuses) // ['pending', 'active']
const allEvents = ['click', 'mouseover', 'keydown', 'keyup', 'scroll', 'mouseout']
const mouseEvents = extract(allEvents, ['click', 'mouseover', 'mouseout', 'mouseenter'])
console.log('\n=== Extract ===')
console.log('Mouse события:', mouseEvents) // ['click', 'mouseover', 'mouseout']
const keyEvents = allEvents.filter(e => e.startsWith('key'))
console.log('Key события:', keyEvents) // ['keydown', 'keyup']
console.log('\n=== NonNullable ===')
const mixed = [1, null, 'hello', undefined, false, 0, '', null, 42]
const defined = nonNullable(mixed)
console.log('Без null/undefined:', defined) // [1, 'hello', false, 0, '', 42]
console.log('\n=== KeysOfType ===')
const formData = {
name: 'Алексей',
age: 30,
email: 'alex@mail.ru',
active: true,
score: 98.5,
role: 'admin',
}
const stringKeys = keysOfType(formData, v => typeof v === 'string')
const numberKeys = keysOfType(formData, v => typeof v === 'number')
const booleanKeys = keysOfType(formData, v => typeof v === 'boolean')
console.log('Строковые поля:', stringKeys) // ['name', 'email', 'role']
console.log('Числовые поля:', numberKeys) // ['age', 'score']
console.log('Булевы поля:', booleanKeys) // ['active']
console.log('\n=== PickByValue / OmitByValue ===')
const stringOnly = pickByValue(formData, v => typeof v === 'string')
console.log('Только строки:', stringOnly)
const noStrings = omitByValue(formData, v => typeof v === 'string')
console.log('Без строк:', noStrings)
console.log('\n=== CommonKeys ===')
const user = { id: 1, name: 'Алексей', email: 'a@b.ru', role: 'user' }
const profile = { id: 1, name: 'Алексей', avatar: 'img.png', bio: 'Dev' }
const shared = commonKeys(user, profile)
console.log('Общие ключи:', shared) // ['id', 'name']Реализуй функцию `difference(arr1, arr2)` — возвращает элементы из arr1, которых нет в arr2 (аналог Exclude). Реализуй функцию `intersection(arr1, arr2)` — возвращает элементы, присутствующие в обоих массивах (аналог Extract). Реализуй функцию `symmetricDifference(arr1, arr2)` — возвращает элементы, которые есть в одном массиве но не в другом (объединение difference в обе стороны). Все функции не должны содержать дубликатов в результате.
difference: const set2 = new Set(arr2); return [...new Set(arr1)].filter(x => !set2.has(x)). intersection: const set2 = new Set(arr2); return [...new Set(arr1)].filter(x => set2.has(x)). symmetricDifference: return [...difference(arr1, arr2), ...difference(arr2, arr1)].
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке