Mapped types позволяют создавать новые типы путём итерации по ключам существующего типа — как map() для массивов, но для типов:
// Как Partial<T> реализован в lib.d.ts:
type Partial<T> = {
[K in keyof T]?: T[K]
}
// Как Readonly<T> реализован в lib.d.ts:
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
interface User {
name: string
age: number
email: string
}
type PartialUser = Partial<User>
// { name?: string; age?: number; email?: string }
type ReadonlyUser = Readonly<User>
// { readonly name: string; readonly age: number; readonly email: string }Плюс добавляет модификатор, минус убирает:
// Required<T> — убирает опциональность (минус у ?)
type Required<T> = {
[K in keyof T]-?: T[K]
}
// Mutable<T> — убирает readonly (минус у readonly)
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
interface Config {
readonly host: string
port?: number
}
type MutableConfig = Mutable<Config>
// { host: string; port?: number } — readonly убран
type RequiredConfig = Required<Config>
// { readonly host: string; port: number } — ? убранПозволяют строить типы строк с шаблонами:
type EventName = `on${Capitalize<string>}`
// 'onClick', 'onChange', 'onSubmit', ...
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface User { name: string; age: number }
type UserGetters = Getters<User>
// { getName: () => string; getAge: () => number }// EventMap — создаёт обработчики событий для каждого поля
type EventMap<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void
}
interface Form { username: string; password: string }
type FormEvents = EventMap<Form>
// {
// onUsernameChange: (value: string) => void
// onPasswordChange: (value: string) => void
// }// Делает все поля nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
// Только указанные ключи опциональны
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
// Все значения — строки (для форм)
type Stringify<T> = {
[K in keyof T]: string
}
// keyof и typeof вместе
const palette = { red: '#ff0000', green: '#00ff00', blue: '#0000ff' }
type ColorKey = keyof typeof palette // 'red' | 'green' | 'blue'makeGetters и makeProxy — mapped-type-like паттерны в JS: автогенерация методов для полей объекта
// Mapped types в TypeScript генерируют новые типы из существующих.
// В JavaScript мы реализуем те же паттерны динамически в runtime.
// makeGetters — создаёт объект с методами getX() для каждого поля
function makeGetters(obj) {
const getters = {}
Object.keys(obj).forEach(key => {
const getterName = 'get' + key[0].toUpperCase() + key.slice(1)
getters[getterName] = () => obj[key]
})
return getters
}
// makeGettersAndSetters — геттеры и сеттеры через замыкания
function makeGettersAndSetters(initialObj) {
const state = Object.assign({}, initialObj)
const api = {}
Object.keys(state).forEach(key => {
const capitalized = key[0].toUpperCase() + key.slice(1)
api['get' + capitalized] = () => state[key]
api['set' + capitalized] = (value) => { state[key] = value }
})
return api
}
// makeProxy — Proxy с логированием (как mapped type + логика)
function makeProxy(obj) {
return new Proxy(obj, {
get(target, prop) {
console.log(`GET ${String(prop)}: ${JSON.stringify(target[prop])}`)
return target[prop]
},
set(target, prop, value) {
console.log(`SET ${String(prop)}: ${JSON.stringify(target[prop])} -> ${JSON.stringify(value)}`)
target[prop] = value
return true
}
})
}
// makeEventMap — создаёт обработчики onXChange для каждого поля
// (аналог EventMap<T> из TypeScript)
function makeEventMap(obj) {
const handlers = {}
const listeners = {}
Object.keys(obj).forEach(key => {
const eventName = 'on' + key[0].toUpperCase() + key.slice(1) + 'Change'
listeners[key] = []
handlers[eventName] = (callback) => {
listeners[key].push(callback)
}
})
// Обёртка над объектом: при изменении вызывает слушателей
const proxy = new Proxy(obj, {
set(target, prop, value) {
const old = target[prop]
target[prop] = value
if (prop in listeners) {
listeners[prop].forEach(cb => cb(value, old))
}
return true
}
})
return { proxy, handlers }
}
// --- Демонстрация ---
const user = { name: 'Алексей', age: 30, email: 'alex@example.com' }
console.log('=== makeGetters ===')
const getters = makeGetters(user)
console.log(getters.getName()) // 'Алексей'
console.log(getters.getAge()) // 30
console.log(getters.getEmail()) // 'alex@example.com'
console.log('\n=== makeGettersAndSetters ===')
const store = makeGettersAndSetters({ name: 'Иван', score: 100 })
console.log(store.getName()) // 'Иван'
store.setName('Пётр')
console.log(store.getName()) // 'Пётр'
store.setScore(store.getScore() + 50)
console.log(store.getScore()) // 150
console.log('\n=== makeEventMap ===')
const { proxy: form, handlers } = makeEventMap({ username: '', email: '' })
handlers.onUsernameChange((newVal) => console.log('username изменился на:', newVal))
handlers.onEmailChange((newVal) => console.log('email изменился на:', newVal))
form.username = 'john_doe' // username изменился на: john_doe
form.email = 'john@test.com' // email изменился на: john@test.comMapped types позволяют создавать новые типы путём итерации по ключам существующего типа — как map() для массивов, но для типов:
// Как Partial<T> реализован в lib.d.ts:
type Partial<T> = {
[K in keyof T]?: T[K]
}
// Как Readonly<T> реализован в lib.d.ts:
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
interface User {
name: string
age: number
email: string
}
type PartialUser = Partial<User>
// { name?: string; age?: number; email?: string }
type ReadonlyUser = Readonly<User>
// { readonly name: string; readonly age: number; readonly email: string }Плюс добавляет модификатор, минус убирает:
// Required<T> — убирает опциональность (минус у ?)
type Required<T> = {
[K in keyof T]-?: T[K]
}
// Mutable<T> — убирает readonly (минус у readonly)
type Mutable<T> = {
-readonly [K in keyof T]: T[K]
}
interface Config {
readonly host: string
port?: number
}
type MutableConfig = Mutable<Config>
// { host: string; port?: number } — readonly убран
type RequiredConfig = Required<Config>
// { readonly host: string; port: number } — ? убранПозволяют строить типы строк с шаблонами:
type EventName = `on${Capitalize<string>}`
// 'onClick', 'onChange', 'onSubmit', ...
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface User { name: string; age: number }
type UserGetters = Getters<User>
// { getName: () => string; getAge: () => number }// EventMap — создаёт обработчики событий для каждого поля
type EventMap<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void
}
interface Form { username: string; password: string }
type FormEvents = EventMap<Form>
// {
// onUsernameChange: (value: string) => void
// onPasswordChange: (value: string) => void
// }// Делает все поля nullable
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
// Только указанные ключи опциональны
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
// Все значения — строки (для форм)
type Stringify<T> = {
[K in keyof T]: string
}
// keyof и typeof вместе
const palette = { red: '#ff0000', green: '#00ff00', blue: '#0000ff' }
type ColorKey = keyof typeof palette // 'red' | 'green' | 'blue'makeGetters и makeProxy — mapped-type-like паттерны в JS: автогенерация методов для полей объекта
// Mapped types в TypeScript генерируют новые типы из существующих.
// В JavaScript мы реализуем те же паттерны динамически в runtime.
// makeGetters — создаёт объект с методами getX() для каждого поля
function makeGetters(obj) {
const getters = {}
Object.keys(obj).forEach(key => {
const getterName = 'get' + key[0].toUpperCase() + key.slice(1)
getters[getterName] = () => obj[key]
})
return getters
}
// makeGettersAndSetters — геттеры и сеттеры через замыкания
function makeGettersAndSetters(initialObj) {
const state = Object.assign({}, initialObj)
const api = {}
Object.keys(state).forEach(key => {
const capitalized = key[0].toUpperCase() + key.slice(1)
api['get' + capitalized] = () => state[key]
api['set' + capitalized] = (value) => { state[key] = value }
})
return api
}
// makeProxy — Proxy с логированием (как mapped type + логика)
function makeProxy(obj) {
return new Proxy(obj, {
get(target, prop) {
console.log(`GET ${String(prop)}: ${JSON.stringify(target[prop])}`)
return target[prop]
},
set(target, prop, value) {
console.log(`SET ${String(prop)}: ${JSON.stringify(target[prop])} -> ${JSON.stringify(value)}`)
target[prop] = value
return true
}
})
}
// makeEventMap — создаёт обработчики onXChange для каждого поля
// (аналог EventMap<T> из TypeScript)
function makeEventMap(obj) {
const handlers = {}
const listeners = {}
Object.keys(obj).forEach(key => {
const eventName = 'on' + key[0].toUpperCase() + key.slice(1) + 'Change'
listeners[key] = []
handlers[eventName] = (callback) => {
listeners[key].push(callback)
}
})
// Обёртка над объектом: при изменении вызывает слушателей
const proxy = new Proxy(obj, {
set(target, prop, value) {
const old = target[prop]
target[prop] = value
if (prop in listeners) {
listeners[prop].forEach(cb => cb(value, old))
}
return true
}
})
return { proxy, handlers }
}
// --- Демонстрация ---
const user = { name: 'Алексей', age: 30, email: 'alex@example.com' }
console.log('=== makeGetters ===')
const getters = makeGetters(user)
console.log(getters.getName()) // 'Алексей'
console.log(getters.getAge()) // 30
console.log(getters.getEmail()) // 'alex@example.com'
console.log('\n=== makeGettersAndSetters ===')
const store = makeGettersAndSetters({ name: 'Иван', score: 100 })
console.log(store.getName()) // 'Иван'
store.setName('Пётр')
console.log(store.getName()) // 'Пётр'
store.setScore(store.getScore() + 50)
console.log(store.getScore()) // 150
console.log('\n=== makeEventMap ===')
const { proxy: form, handlers } = makeEventMap({ username: '', email: '' })
handlers.onUsernameChange((newVal) => console.log('username изменился на:', newVal))
handlers.onEmailChange((newVal) => console.log('email изменился на:', newVal))
form.username = 'john_doe' // username изменился на: john_doe
form.email = 'john@test.com' // email изменился на: john@test.comРеализуй `createStore(initialState)` — возвращает объект с геттерами и сеттерами для каждого поля. Для поля `name` должны быть методы `getName()` (возвращает значение) и `setName(v)` (обновляет значение). Используй замыкания для хранения состояния.
Скопируй initialState через Object.assign({}, initialState). Для каждого ключа: capitalize = key[0].toUpperCase() + key.slice(1). Геттер: () => state[key]. Сеттер: (value) => { state[key] = value }. Замыкание на переменную state гарантирует изоляцию.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке