Без ограничений generic-тип T может быть чем угодно, и TypeScript не позволит обращаться к его свойствам:
function getLength<T>(value: T): number {
return value.length // Ошибка TS: Property 'length' does not exist on type 'T'
}
// Решение — ограничить T типами у которых есть length:
function getLength<T extends { length: number }>(value: T): number {
return value.length // OK!
}
getLength('hello') // 5 — string имеет length
getLength([1, 2, 3]) // 3 — array имеет length
// getLength(42) // Ошибка TS: number не удовлетворяет { length: number }extends задаёт минимальный контракт для T:
interface HasId {
id: number
}
// T должен иметь поле id
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id)
}
const users = [{ id: 1, name: 'Алексей' }, { id: 2, name: 'Ольга' }]
const found = findById(users, 1) // { id: 1, name: 'Алексей' } — тип User, не HasId!
// Множественные ограничения через intersection:
function process<T extends HasId & { name: string }>(item: T): string {
return `#${item.id}: ${item.name}`
}keyof T — тип, объединяющий все ключи T. Используется для безопасного обращения к свойствам:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key] // TypeScript знает точный тип T[K]
}
const user = { name: 'Алексей', age: 30, active: true }
const name = getProperty(user, 'name') // тип string
const age = getProperty(user, 'age') // тип number
// getProperty(user, 'email') // Ошибка TS: 'email' не является ключом
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return { ...obj, [key]: value }
}Ограничения позволяют TypeScript выводить точные типы результата:
// Возвращаемый тип зависит от переданного ключа
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key])
}
const users = [
{ name: 'Алексей', age: 30 },
{ name: 'Ольга', age: 25 },
]
const names = pluck(users, 'name') // string[] — TypeScript знает тип!
const ages = pluck(users, 'age') // number[]// Преобразуем массив объектов в Map по ключу
function groupBy<T, K extends keyof T>(
items: T[],
key: K
): Map<T[K], T[]> {
const map = new Map<T[K], T[]>()
for (const item of items) {
const k = item[key]
const group = map.get(k) ?? []
map.set(k, [...group, item])
}
return map
}
const orders = [
{ id: 1, status: 'pending', amount: 100 },
{ id: 2, status: 'done', amount: 200 },
{ id: 3, status: 'pending', amount: 50 },
]
const byStatus = groupBy(orders, 'status')
// Map { 'pending' => [{...}, {...}], 'done' => [{...}] }// T по умолчанию object, но можно передать другой тип
function createStore<T extends object = Record<string, unknown>>(
initial: T
): { get(): T; set(updates: Partial<T>): void } {
let state = { ...initial }
return {
get: () => ({ ...state }),
set: (updates) => { state = { ...state, ...updates } }
}
}Runtime реализация getProperty, pluck и groupBy — типобезопасные операции с объектами в стиле TypeScript generics
// TypeScript: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]
// JS: реализуем с runtime-проверками
function getProperty(obj, key) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
throw new RangeError(
`Ключ "${String(key)}" не существует. Доступные: ${Object.keys(obj).join(', ')}`
)
}
return obj[key]
}
function setProperty(obj, key, value) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
throw new RangeError(`Ключ "${String(key)}" не существует в объекте`)
}
return { ...obj, [key]: value }
}
// TypeScript: function pluck<T, K extends keyof T>(items: T[], key: K): T[K][]
function pluck(items, key) {
return items.map(item => {
if (!Object.prototype.hasOwnProperty.call(item, key)) {
throw new RangeError(`Элемент не имеет ключа "${String(key)}"`)
}
return item[key]
})
}
// TypeScript: function groupBy<T, K extends keyof T>(items: T[], key: K): Map<T[K], T[]>
function groupBy(items, key) {
const map = new Map()
for (const item of items) {
const k = item[key]
if (!map.has(k)) map.set(k, [])
map.get(k).push(item)
}
return map
}
// TypeScript: function sortBy<T, K extends keyof T>(items: T[], key: K): T[]
function sortBy(items, key) {
return [...items].sort((a, b) => {
const va = a[key]
const vb = b[key]
if (va < vb) return -1
if (va > vb) return 1
return 0
})
}
// --- Демонстрация ---
const users = [
{ id: 3, name: 'Виктор', age: 35, role: 'user' },
{ id: 1, name: 'Алексей', age: 30, role: 'admin' },
{ id: 4, name: 'Мария', age: 28, role: 'user' },
{ id: 2, name: 'Ольга', age: 25, role: 'admin' },
]
console.log('=== getProperty ===')
console.log(getProperty(users[0], 'name')) // 'Виктор'
console.log(getProperty(users[0], 'age')) // 35
try {
getProperty(users[0], 'email') // ключ не существует
} catch (e) {
console.log('Ошибка:', e.message)
}
console.log('\n=== setProperty (иммутабельный) ===')
const updated = setProperty(users[0], 'age', 36)
console.log('Обновлённый:', updated.age) // 36
console.log('Оригинал:', users[0].age) // 35 — не изменился
console.log('\n=== pluck ===')
const names = pluck(users, 'name')
console.log('Имена:', names)
const ages = pluck(users, 'age')
console.log('Возрасты:', ages)
console.log('\n=== groupBy ===')
const byRole = groupBy(users, 'role')
console.log('Администраторы:', byRole.get('admin').map(u => u.name))
console.log('Пользователи:', byRole.get('user').map(u => u.name))
console.log('\n=== sortBy ===')
const byAge = sortBy(users, 'age')
const byName = sortBy(users, 'name')
console.log('По возрасту:', pluck(byAge, 'name'))
console.log('По имени:', pluck(byName, 'name'))Без ограничений generic-тип T может быть чем угодно, и TypeScript не позволит обращаться к его свойствам:
function getLength<T>(value: T): number {
return value.length // Ошибка TS: Property 'length' does not exist on type 'T'
}
// Решение — ограничить T типами у которых есть length:
function getLength<T extends { length: number }>(value: T): number {
return value.length // OK!
}
getLength('hello') // 5 — string имеет length
getLength([1, 2, 3]) // 3 — array имеет length
// getLength(42) // Ошибка TS: number не удовлетворяет { length: number }extends задаёт минимальный контракт для T:
interface HasId {
id: number
}
// T должен иметь поле id
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id)
}
const users = [{ id: 1, name: 'Алексей' }, { id: 2, name: 'Ольга' }]
const found = findById(users, 1) // { id: 1, name: 'Алексей' } — тип User, не HasId!
// Множественные ограничения через intersection:
function process<T extends HasId & { name: string }>(item: T): string {
return `#${item.id}: ${item.name}`
}keyof T — тип, объединяющий все ключи T. Используется для безопасного обращения к свойствам:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key] // TypeScript знает точный тип T[K]
}
const user = { name: 'Алексей', age: 30, active: true }
const name = getProperty(user, 'name') // тип string
const age = getProperty(user, 'age') // тип number
// getProperty(user, 'email') // Ошибка TS: 'email' не является ключом
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return { ...obj, [key]: value }
}Ограничения позволяют TypeScript выводить точные типы результата:
// Возвращаемый тип зависит от переданного ключа
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key])
}
const users = [
{ name: 'Алексей', age: 30 },
{ name: 'Ольга', age: 25 },
]
const names = pluck(users, 'name') // string[] — TypeScript знает тип!
const ages = pluck(users, 'age') // number[]// Преобразуем массив объектов в Map по ключу
function groupBy<T, K extends keyof T>(
items: T[],
key: K
): Map<T[K], T[]> {
const map = new Map<T[K], T[]>()
for (const item of items) {
const k = item[key]
const group = map.get(k) ?? []
map.set(k, [...group, item])
}
return map
}
const orders = [
{ id: 1, status: 'pending', amount: 100 },
{ id: 2, status: 'done', amount: 200 },
{ id: 3, status: 'pending', amount: 50 },
]
const byStatus = groupBy(orders, 'status')
// Map { 'pending' => [{...}, {...}], 'done' => [{...}] }// T по умолчанию object, но можно передать другой тип
function createStore<T extends object = Record<string, unknown>>(
initial: T
): { get(): T; set(updates: Partial<T>): void } {
let state = { ...initial }
return {
get: () => ({ ...state }),
set: (updates) => { state = { ...state, ...updates } }
}
}Runtime реализация getProperty, pluck и groupBy — типобезопасные операции с объектами в стиле TypeScript generics
// TypeScript: function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]
// JS: реализуем с runtime-проверками
function getProperty(obj, key) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
throw new RangeError(
`Ключ "${String(key)}" не существует. Доступные: ${Object.keys(obj).join(', ')}`
)
}
return obj[key]
}
function setProperty(obj, key, value) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
throw new RangeError(`Ключ "${String(key)}" не существует в объекте`)
}
return { ...obj, [key]: value }
}
// TypeScript: function pluck<T, K extends keyof T>(items: T[], key: K): T[K][]
function pluck(items, key) {
return items.map(item => {
if (!Object.prototype.hasOwnProperty.call(item, key)) {
throw new RangeError(`Элемент не имеет ключа "${String(key)}"`)
}
return item[key]
})
}
// TypeScript: function groupBy<T, K extends keyof T>(items: T[], key: K): Map<T[K], T[]>
function groupBy(items, key) {
const map = new Map()
for (const item of items) {
const k = item[key]
if (!map.has(k)) map.set(k, [])
map.get(k).push(item)
}
return map
}
// TypeScript: function sortBy<T, K extends keyof T>(items: T[], key: K): T[]
function sortBy(items, key) {
return [...items].sort((a, b) => {
const va = a[key]
const vb = b[key]
if (va < vb) return -1
if (va > vb) return 1
return 0
})
}
// --- Демонстрация ---
const users = [
{ id: 3, name: 'Виктор', age: 35, role: 'user' },
{ id: 1, name: 'Алексей', age: 30, role: 'admin' },
{ id: 4, name: 'Мария', age: 28, role: 'user' },
{ id: 2, name: 'Ольга', age: 25, role: 'admin' },
]
console.log('=== getProperty ===')
console.log(getProperty(users[0], 'name')) // 'Виктор'
console.log(getProperty(users[0], 'age')) // 35
try {
getProperty(users[0], 'email') // ключ не существует
} catch (e) {
console.log('Ошибка:', e.message)
}
console.log('\n=== setProperty (иммутабельный) ===')
const updated = setProperty(users[0], 'age', 36)
console.log('Обновлённый:', updated.age) // 36
console.log('Оригинал:', users[0].age) // 35 — не изменился
console.log('\n=== pluck ===')
const names = pluck(users, 'name')
console.log('Имена:', names)
const ages = pluck(users, 'age')
console.log('Возрасты:', ages)
console.log('\n=== groupBy ===')
const byRole = groupBy(users, 'role')
console.log('Администраторы:', byRole.get('admin').map(u => u.name))
console.log('Пользователи:', byRole.get('user').map(u => u.name))
console.log('\n=== sortBy ===')
const byAge = sortBy(users, 'age')
const byName = sortBy(users, 'name')
console.log('По возрасту:', pluck(byAge, 'name'))
console.log('По имени:', pluck(byName, 'name'))Реализуй функцию `filterByProperty(items, key, value)`, которая фильтрует массив объектов по значению указанного ключа. Реализуй функцию `uniqueBy(items, key)`, которая удаляет дубликаты из массива объектов по значению ключа (сохраняет первое вхождение). Обе функции не должны мутировать исходный массив.
filterByProperty: return items.filter(item => item[key] === value). uniqueBy: const seen = new Set(); return items.filter(item => { const v = item[key]; if (seen.has(v)) return false; seen.add(v); return true; }).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке