Module Augmentation позволяет добавлять новые типы в уже существующие модули — включая сторонние библиотеки — без изменения их исходного кода. Это мощный инструмент, когда нужно расширить типы npm-пакета или добавить собственные поля к существующим интерфейсам.
Допустим, библиотека express не знает о вашем поле user на объекте Request. Вы можете добавить его через declare module:
// types/express.d.ts
import { User } from './models/User'
declare module 'express-serve-static-core' {
interface Request {
user?: User
sessionId?: string
}
}Теперь везде в проекте req.user будет иметь правильный тип.
TypeScript поддерживает **declaration merging** — одноимённые интерфейсы сливаются:
// lib.ts — оригинальный интерфейс
interface Config {
debug: boolean
port: number
}
// my-module.ts — расширение в том же проекте
interface Config {
customField: string // добавляется к оригинальному Config
}
// Итог: Config имеет debug, port И customField
const config: Config = {
debug: true,
port: 3000,
customField: 'hello', // OK
}Можно добавлять типы в глобальное пространство имён — например, в Window:
// types/global.d.ts
declare global {
interface Window {
analytics: {
track(event: string, data?: Record<string, unknown>): void
}
__APP_VERSION__: string
}
// Расширение встроенного Array
interface Array<T> {
last(): T | undefined
}
}
// Реализация:
Array.prototype.last = function() {
return this[this.length - 1]
}
export {} // Делает файл модулем, не скриптомДля библиотек с namespace-декларациями:
// Расширяем namespace библиотеки
declare namespace MyLib {
interface Options {
timeout: number
retries: number
// Добавляем своё поле:
customHeaders?: Record<string, string>
}
}Паттерн для обёртки сторонней библиотеки с расширенными типами:
// my-axios.ts
import axios from 'axios'
declare module 'axios' {
interface AxiosRequestConfig {
_retry?: boolean
_retryCount?: number
}
}
export default axios1. Файл с declare module должен быть **модулем** — содержать хотя бы один import или export {}.
2. Глобальные аугментации требуют declare global { }.
3. Нельзя добавлять новые **type alias** через аугментацию — только члены интерфейса или namespace.
4. Файлы .d.ts с аугментациями должны быть в include вашего tsconfig.
expect.extend@typesСимуляция module augmentation: расширение объектов через Object.assign и прототипы — JS-эквивалент добавления полей к чужим типам
// В TypeScript declare module расширяет ТИПЫ существующих модулей.
// В JS мы можем симулировать это через прототипы и monkey-patching.
// --- Симуляция: расширение "чужой" библиотеки ---
// Допустим, это код сторонней библиотеки (мы не можем его менять):
class LibraryRequest {
constructor(path, method = 'GET') {
this.path = path
this.method = method
this.headers = {}
}
getPath() { return this.path }
getMethod() { return this.method }
}
class LibraryResponse {
constructor(statusCode, body) {
this.statusCode = statusCode
this.body = body
}
send() {
return `HTTP ${this.statusCode}: ${JSON.stringify(this.body)}`
}
}
// --- Module Augmentation в JS: добавляем поля через прототип ---
// TypeScript: declare module 'library' { interface Request { user?: User } }
LibraryRequest.prototype.user = null
LibraryRequest.prototype.sessionId = null
LibraryRequest.prototype.setUser = function(user) {
this.user = user
return this
}
LibraryRequest.prototype.getUser = function() {
return this.user
}
// --- Глобальная аугментация Array (как declare global) ---
// TypeScript: declare global { interface Array<T> { last(): T | undefined } }
if (!Array.prototype.last) {
Array.prototype.last = function() {
return this[this.length - 1]
}
}
if (!Array.prototype.first) {
Array.prototype.first = function() {
return this[0]
}
}
// --- Declaration Merging симуляция: слияние объектов ---
function createConfig(base, extension) {
// Аналог слияния двух interface Config { ... }
return Object.assign({}, base, extension)
}
const baseConfig = { debug: false, port: 3000 }
const myExtension = { customField: 'hello', timeout: 5000 }
const mergedConfig = createConfig(baseConfig, myExtension)
// --- Демонстрация ---
console.log('=== Расширение LibraryRequest ===')
const req = new LibraryRequest('/api/users', 'GET')
req.setUser({ id: 1, name: 'Иван', role: 'admin' })
req.sessionId = 'abc-123'
console.log('path:', req.getPath())
console.log('method:', req.getMethod())
console.log('user:', req.getUser())
console.log('sessionId:', req.sessionId)
console.log('\n=== Middleware pipeline (как Express) ===')
function authMiddleware(req, res, next) {
// Имитируем JWT decode
req.user = { id: 42, name: 'Мария', role: 'user' }
console.log('authMiddleware: user установлен')
next()
}
function adminMiddleware(req, res, next) {
if (req.user?.role !== 'admin') {
console.log('adminMiddleware: доступ запрещён для', req.user?.role)
return
}
next()
}
const req2 = new LibraryRequest('/admin/users', 'GET')
const res2 = new LibraryResponse(200, { users: [] })
let nextCalled = false
authMiddleware(req2, res2, () => {
nextCalled = true
adminMiddleware(req2, res2, () => {
console.log('Доступ разрешён! Ответ:', res2.send())
})
})
console.log('\n=== Глобальная аугментация Array ===')
const numbers = [1, 2, 3, 4, 5]
console.log('last():', numbers.last()) // 5
console.log('first():', numbers.first()) // 1
const words = ['TypeScript', 'JavaScript', 'Rust']
console.log('last():', words.last()) // Rust
console.log('first():', words.first()) // TypeScript
console.log('\n=== Declaration Merging (слияние конфигов) ===')
console.log('mergedConfig:', mergedConfig)
// { debug: false, port: 3000, customField: 'hello', timeout: 5000 }Module Augmentation позволяет добавлять новые типы в уже существующие модули — включая сторонние библиотеки — без изменения их исходного кода. Это мощный инструмент, когда нужно расширить типы npm-пакета или добавить собственные поля к существующим интерфейсам.
Допустим, библиотека express не знает о вашем поле user на объекте Request. Вы можете добавить его через declare module:
// types/express.d.ts
import { User } from './models/User'
declare module 'express-serve-static-core' {
interface Request {
user?: User
sessionId?: string
}
}Теперь везде в проекте req.user будет иметь правильный тип.
TypeScript поддерживает **declaration merging** — одноимённые интерфейсы сливаются:
// lib.ts — оригинальный интерфейс
interface Config {
debug: boolean
port: number
}
// my-module.ts — расширение в том же проекте
interface Config {
customField: string // добавляется к оригинальному Config
}
// Итог: Config имеет debug, port И customField
const config: Config = {
debug: true,
port: 3000,
customField: 'hello', // OK
}Можно добавлять типы в глобальное пространство имён — например, в Window:
// types/global.d.ts
declare global {
interface Window {
analytics: {
track(event: string, data?: Record<string, unknown>): void
}
__APP_VERSION__: string
}
// Расширение встроенного Array
interface Array<T> {
last(): T | undefined
}
}
// Реализация:
Array.prototype.last = function() {
return this[this.length - 1]
}
export {} // Делает файл модулем, не скриптомДля библиотек с namespace-декларациями:
// Расширяем namespace библиотеки
declare namespace MyLib {
interface Options {
timeout: number
retries: number
// Добавляем своё поле:
customHeaders?: Record<string, string>
}
}Паттерн для обёртки сторонней библиотеки с расширенными типами:
// my-axios.ts
import axios from 'axios'
declare module 'axios' {
interface AxiosRequestConfig {
_retry?: boolean
_retryCount?: number
}
}
export default axios1. Файл с declare module должен быть **модулем** — содержать хотя бы один import или export {}.
2. Глобальные аугментации требуют declare global { }.
3. Нельзя добавлять новые **type alias** через аугментацию — только члены интерфейса или namespace.
4. Файлы .d.ts с аугментациями должны быть в include вашего tsconfig.
expect.extend@typesСимуляция module augmentation: расширение объектов через Object.assign и прототипы — JS-эквивалент добавления полей к чужим типам
// В TypeScript declare module расширяет ТИПЫ существующих модулей.
// В JS мы можем симулировать это через прототипы и monkey-patching.
// --- Симуляция: расширение "чужой" библиотеки ---
// Допустим, это код сторонней библиотеки (мы не можем его менять):
class LibraryRequest {
constructor(path, method = 'GET') {
this.path = path
this.method = method
this.headers = {}
}
getPath() { return this.path }
getMethod() { return this.method }
}
class LibraryResponse {
constructor(statusCode, body) {
this.statusCode = statusCode
this.body = body
}
send() {
return `HTTP ${this.statusCode}: ${JSON.stringify(this.body)}`
}
}
// --- Module Augmentation в JS: добавляем поля через прототип ---
// TypeScript: declare module 'library' { interface Request { user?: User } }
LibraryRequest.prototype.user = null
LibraryRequest.prototype.sessionId = null
LibraryRequest.prototype.setUser = function(user) {
this.user = user
return this
}
LibraryRequest.prototype.getUser = function() {
return this.user
}
// --- Глобальная аугментация Array (как declare global) ---
// TypeScript: declare global { interface Array<T> { last(): T | undefined } }
if (!Array.prototype.last) {
Array.prototype.last = function() {
return this[this.length - 1]
}
}
if (!Array.prototype.first) {
Array.prototype.first = function() {
return this[0]
}
}
// --- Declaration Merging симуляция: слияние объектов ---
function createConfig(base, extension) {
// Аналог слияния двух interface Config { ... }
return Object.assign({}, base, extension)
}
const baseConfig = { debug: false, port: 3000 }
const myExtension = { customField: 'hello', timeout: 5000 }
const mergedConfig = createConfig(baseConfig, myExtension)
// --- Демонстрация ---
console.log('=== Расширение LibraryRequest ===')
const req = new LibraryRequest('/api/users', 'GET')
req.setUser({ id: 1, name: 'Иван', role: 'admin' })
req.sessionId = 'abc-123'
console.log('path:', req.getPath())
console.log('method:', req.getMethod())
console.log('user:', req.getUser())
console.log('sessionId:', req.sessionId)
console.log('\n=== Middleware pipeline (как Express) ===')
function authMiddleware(req, res, next) {
// Имитируем JWT decode
req.user = { id: 42, name: 'Мария', role: 'user' }
console.log('authMiddleware: user установлен')
next()
}
function adminMiddleware(req, res, next) {
if (req.user?.role !== 'admin') {
console.log('adminMiddleware: доступ запрещён для', req.user?.role)
return
}
next()
}
const req2 = new LibraryRequest('/admin/users', 'GET')
const res2 = new LibraryResponse(200, { users: [] })
let nextCalled = false
authMiddleware(req2, res2, () => {
nextCalled = true
adminMiddleware(req2, res2, () => {
console.log('Доступ разрешён! Ответ:', res2.send())
})
})
console.log('\n=== Глобальная аугментация Array ===')
const numbers = [1, 2, 3, 4, 5]
console.log('last():', numbers.last()) // 5
console.log('first():', numbers.first()) // 1
const words = ['TypeScript', 'JavaScript', 'Rust']
console.log('last():', words.last()) // Rust
console.log('first():', words.first()) // TypeScript
console.log('\n=== Declaration Merging (слияние конфигов) ===')
console.log('mergedConfig:', mergedConfig)
// { debug: false, port: 3000, customField: 'hello', timeout: 5000 }Реализуй функцию `augmentPrototype(TargetClass, methods)`, которая добавляет методы из объекта `methods` на прототип `TargetClass` (только если метода ещё нет). Затем расширь класс `EventTarget` методами `onceOn(event, handler)` (подписка, которая автоматически отписывается после первого вызова) и `hasListeners(event)` (возвращает true/false). Используй `augmentPrototype` для этого расширения.
В augmentPrototype: Object.keys(methods).forEach(key => { if (!TargetClass.prototype[key]) TargetClass.prototype[key] = methods[key] }). В onceOn: создай wrapper = (data) => { handler(data); this.off(event, wrapper) }, затем this.on(event, wrapper). В hasListeners: return !!(this._listeners[event] && this._listeners[event].length > 0).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке