// Ошибка:
const element = document.getElementById('app')
element.innerHTML = 'Hello' // Object is possibly null
// Решения:
// 1. Optional chaining:
element?.innerHTML // безопасно — ничего не делает если null
// 2. Non-null assertion (если уверены):
element!.innerHTML // говорим TS "доверяй мне"
// 3. Type guard (лучшее решение):
if (element !== null) {
element.innerHTML = 'Hello' // TypeScript знает что не null
}
// 4. Nullish coalescing:
const content = element ?? document.createElement('div')interface User { name: string; email: string }
const user: User = { name: 'Алексей', email: 'a@b.com' }
user.age // Property 'age' does not exist on type 'User'
// Решения:
// 1. Добавить свойство в интерфейс (правильно)
interface User { name: string; email: string; age?: number }
// 2. Использовать type assertion (когда уверены):
(user as any).age // работает, но теряем типобезопасность
// 3. Расширить тип:
type UserWithAge = User & { age: number }// Ошибка:
const status: 'active' | 'inactive' = 'pending' // не входит в union
// Решение — проверить доступные значения
type Status = 'active' | 'inactive' | 'pending'
const status: Status = 'pending' // OKfunction greet(name: string) { console.log(name) }
greet(42) // Argument of type 'number' is not assignable to parameter of type 'string'
// Решение:
greet(String(42)) // явное преобразование
greet(42 + '') // или через конкатенацию
// Общий паттерн — перегрузки:
function greet(name: string | number) {
console.log(String(name))
}// Ошибка при несовпадении аргументов перегрузки
element.addEventListener('customEvent', handler)
// No overload matches this call
// Решение:
element.addEventListener('customEvent', handler as EventListener)
// Или использовать нужную перегрузку// Проблема: тип ссылается сам на себя бесконечно
type Tree = { value: number; children: Tree[] } // OK — массив прерывает рекурсию
// Проблемная рекурсия:
type Infinite = { next: Infinite & { extra: string } } // TS2456// Ошибка с noImplicitAny:
function process(items) { } // Parameter 'items' implicitly has an 'any' type
// Решения:
function process(items: unknown[]) { } // unknown безопаснее any
function process(items: string[]) { } // конкретный тип
function process<T>(items: T[]): T[] { } // дженерикimport styles from './app.module.css' // Cannot find module or type declarations
// Решения:
// 1. Создать declaration file:
// app.module.css.d.ts:
declare module '*.css' {
const styles: Record<string, string>
export default styles
}
// 2. Или добавить в tsconfig:
// { "moduleResolution": "bundler" }const value = 'hello' as number // Ошибка
// Решение через double assertion (с осторожностью!):
const value = 'hello' as unknown as number
// Лучше — явное преобразование:
const value = Number('hello') // NaNclass Timer {
count = 0
start() {
setInterval(function() {
this.count++ // 'this' implicitly has type 'any'
}, 1000)
}
}
// Решения:
// 1. Стрелочная функция (рекомендуется):
setInterval(() => { this.count++ }, 1000)
// 2. Bind:
setInterval(function(this: Timer) { this.count++ }.bind(this), 1000)Демонстрация 10 типичных ошибок TypeScript с правильными решениями — паттерны защитного программирования
// Демонстрируем решения для 10 типичных ошибок TypeScript.
// В TypeScript эти паттерны предотвращают ошибки ещё на этапе компиляции.
// === 1. Null/undefined safety ===
console.log('=== 1. Null Safety ===')
function safeGetElement(id) {
const element = { id, innerHTML: '' } // симуляция DOM
// TS: element?.innerHTML вместо element.innerHTML (если может быть null)
const value = element?.innerHTML ?? 'default'
console.log('Safe access:', value)
// Type guard:
function processElement(el) {
if (el === null || el === undefined) {
console.log('Element is null/undefined — skip')
return
}
console.log('Element found:', el.id) // TypeScript знает что не null
}
processElement(null)
processElement(element)
}
safeGetElement('app')
// === 2. Безопасный доступ к свойствам ===
console.log('\n=== 2. Property Access ===')
// Паттерн "hasOwnProperty" guard
function getProperty(obj, key) {
// TS: if (key in obj) — type narrowing
if (typeof obj === 'object' && obj !== null && key in obj) {
return obj[key]
}
return undefined
}
const user = { name: 'Алексей', email: 'alex@example.com' }
console.log('name:', getProperty(user, 'name')) // Алексей
console.log('age:', getProperty(user, 'age')) // undefined (нет такого поля)
// === 3. Типобезопасный union ===
console.log('\n=== 3. Union Types ===')
const VALID_STATUSES = ['active', 'inactive', 'pending']
function isValidStatus(value) {
return VALID_STATUSES.includes(value)
}
function setStatus(status) {
if (!isValidStatus(status)) {
throw new Error(`Invalid status: "${status}". Expected: ${VALID_STATUSES.join(', ')}`)
}
return status
}
try { setStatus('unknown') } catch (e) { console.log('Error:', e.message) }
console.log('Valid:', setStatus('active'))
// === 4. Безопасное приведение типов ===
console.log('\n=== 4. Type Conversion ===')
// TS: "hello" as unknown as number — double assertion
// В JS — явное преобразование
function safeToNumber(value) {
const num = Number(value)
if (isNaN(num)) {
console.warn(`Cannot convert "${value}" to number, using 0`)
return 0
}
return num
}
console.log(safeToNumber('42')) // 42
console.log(safeToNumber('abc')) // 0 + warning
console.log(safeToNumber(true)) // 1
console.log(safeToNumber(null)) // 0
// === 5. Circular references ===
console.log('\n=== 5. Circular References ===')
// Безопасная сериализация с циклическими ссылками
function safeStringify(obj) {
const seen = new WeakSet()
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]'
seen.add(value)
}
return value
})
}
const a = { name: 'a' }
const b = { name: 'b', ref: a }
a.ref = b // циклическая ссылка
console.log(safeStringify(a))
// === 6. Generic функции вместо any ===
console.log('\n=== 6. Generics vs any ===')
// TS: function identity<T>(x: T): T
function identity(x) { return x }
// TS: function first<T>(arr: T[]): T | undefined
function first(arr) { return arr[0] }
// TS: function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
function pick(obj, keys) {
return keys.reduce((result, key) => {
if (key in obj) result[key] = obj[key]
return result
}, {})
}
console.log(identity(42))
console.log(first([1, 2, 3]))
console.log(pick({ name: 'Алексей', email: 'a@b.com', role: 'admin' }, ['name', 'email']))
// === 7. Context (this) ===
console.log('\n=== 7. This Context ===')
class Timer {
constructor() { this.count = 0 }
startBroken() {
// TS: 'this' implicitly has type 'any' в обычных функциях
const fn = function() {
// this тут = undefined или globalThis, не Timer
}
return 'broken (this не указывает на Timer)'
}
startFixed() {
// Решение 1: стрелочная функция
const fn = () => {
this.count++ // this = Timer (замыкание)
}
fn()
return 'fixed with arrow function, count = ' + this.count
}
}
const timer = new Timer()
console.log(timer.startFixed())
// === 8. Exhaustive checks (switch) ===
console.log('\n=== 8. Exhaustive Switch ===')
// TS: function assertNever(x: never): never
function assertNever(value, allowedValues) {
throw new Error(`Unhandled value: "${value}". Expected one of: ${allowedValues.join(', ')}`)
}
function handleStatus(status) {
switch (status) {
case 'active': return 'Активен'
case 'inactive': return 'Неактивен'
case 'pending': return 'Ожидает'
default:
// В TS: assertNever(status) — ошибка компиляции если не все кейсы обработаны
return assertNever(status, ['active', 'inactive', 'pending'])
}
}
console.log(handleStatus('active'))
try { handleStatus('unknown') } catch (e) { console.log('Unhandled:', e.message) }
// === 9. Type-safe event system ===
console.log('\n=== 9. Type-safe Events ===')
function createTypedEmitter(validEvents) {
const listeners = {}
return {
on(event, handler) {
if (!validEvents.includes(event)) {
throw new Error(`Unknown event "${event}". Valid: ${validEvents.join(', ')}`)
}
if (!listeners[event]) listeners[event] = []
listeners[event].push(handler)
},
emit(event, data) {
if (!validEvents.includes(event)) {
throw new Error(`Unknown event: ${event}`)
}
;(listeners[event] || []).forEach(fn => fn(data))
}
}
}
const emitter = createTypedEmitter(['click', 'change', 'submit'])
emitter.on('click', data => console.log('click:', data))
emitter.emit('click', { x: 10, y: 20 })
try { emitter.on('unknown', () => {}) } catch (e) { console.log('Event error:', e.message) }
// === 10. Module не найден — runtime check ===
console.log('\n=== 10. Dynamic Import Guard ===')
async function safeImport(modulePath, fallback = null) {
try {
// В реальности: const mod = await import(modulePath)
// Симулируем:
if (modulePath === 'missing-module') throw new Error(`Cannot find module '${modulePath}'`)
return { default: 'loaded module' }
} catch (e) {
console.warn(`Module load failed: ${e.message}`)
return fallback
}
}
safeImport('missing-module').then(mod => {
console.log('Missing module result:', mod) // null
})// Ошибка:
const element = document.getElementById('app')
element.innerHTML = 'Hello' // Object is possibly null
// Решения:
// 1. Optional chaining:
element?.innerHTML // безопасно — ничего не делает если null
// 2. Non-null assertion (если уверены):
element!.innerHTML // говорим TS "доверяй мне"
// 3. Type guard (лучшее решение):
if (element !== null) {
element.innerHTML = 'Hello' // TypeScript знает что не null
}
// 4. Nullish coalescing:
const content = element ?? document.createElement('div')interface User { name: string; email: string }
const user: User = { name: 'Алексей', email: 'a@b.com' }
user.age // Property 'age' does not exist on type 'User'
// Решения:
// 1. Добавить свойство в интерфейс (правильно)
interface User { name: string; email: string; age?: number }
// 2. Использовать type assertion (когда уверены):
(user as any).age // работает, но теряем типобезопасность
// 3. Расширить тип:
type UserWithAge = User & { age: number }// Ошибка:
const status: 'active' | 'inactive' = 'pending' // не входит в union
// Решение — проверить доступные значения
type Status = 'active' | 'inactive' | 'pending'
const status: Status = 'pending' // OKfunction greet(name: string) { console.log(name) }
greet(42) // Argument of type 'number' is not assignable to parameter of type 'string'
// Решение:
greet(String(42)) // явное преобразование
greet(42 + '') // или через конкатенацию
// Общий паттерн — перегрузки:
function greet(name: string | number) {
console.log(String(name))
}// Ошибка при несовпадении аргументов перегрузки
element.addEventListener('customEvent', handler)
// No overload matches this call
// Решение:
element.addEventListener('customEvent', handler as EventListener)
// Или использовать нужную перегрузку// Проблема: тип ссылается сам на себя бесконечно
type Tree = { value: number; children: Tree[] } // OK — массив прерывает рекурсию
// Проблемная рекурсия:
type Infinite = { next: Infinite & { extra: string } } // TS2456// Ошибка с noImplicitAny:
function process(items) { } // Parameter 'items' implicitly has an 'any' type
// Решения:
function process(items: unknown[]) { } // unknown безопаснее any
function process(items: string[]) { } // конкретный тип
function process<T>(items: T[]): T[] { } // дженерикimport styles from './app.module.css' // Cannot find module or type declarations
// Решения:
// 1. Создать declaration file:
// app.module.css.d.ts:
declare module '*.css' {
const styles: Record<string, string>
export default styles
}
// 2. Или добавить в tsconfig:
// { "moduleResolution": "bundler" }const value = 'hello' as number // Ошибка
// Решение через double assertion (с осторожностью!):
const value = 'hello' as unknown as number
// Лучше — явное преобразование:
const value = Number('hello') // NaNclass Timer {
count = 0
start() {
setInterval(function() {
this.count++ // 'this' implicitly has type 'any'
}, 1000)
}
}
// Решения:
// 1. Стрелочная функция (рекомендуется):
setInterval(() => { this.count++ }, 1000)
// 2. Bind:
setInterval(function(this: Timer) { this.count++ }.bind(this), 1000)Демонстрация 10 типичных ошибок TypeScript с правильными решениями — паттерны защитного программирования
// Демонстрируем решения для 10 типичных ошибок TypeScript.
// В TypeScript эти паттерны предотвращают ошибки ещё на этапе компиляции.
// === 1. Null/undefined safety ===
console.log('=== 1. Null Safety ===')
function safeGetElement(id) {
const element = { id, innerHTML: '' } // симуляция DOM
// TS: element?.innerHTML вместо element.innerHTML (если может быть null)
const value = element?.innerHTML ?? 'default'
console.log('Safe access:', value)
// Type guard:
function processElement(el) {
if (el === null || el === undefined) {
console.log('Element is null/undefined — skip')
return
}
console.log('Element found:', el.id) // TypeScript знает что не null
}
processElement(null)
processElement(element)
}
safeGetElement('app')
// === 2. Безопасный доступ к свойствам ===
console.log('\n=== 2. Property Access ===')
// Паттерн "hasOwnProperty" guard
function getProperty(obj, key) {
// TS: if (key in obj) — type narrowing
if (typeof obj === 'object' && obj !== null && key in obj) {
return obj[key]
}
return undefined
}
const user = { name: 'Алексей', email: 'alex@example.com' }
console.log('name:', getProperty(user, 'name')) // Алексей
console.log('age:', getProperty(user, 'age')) // undefined (нет такого поля)
// === 3. Типобезопасный union ===
console.log('\n=== 3. Union Types ===')
const VALID_STATUSES = ['active', 'inactive', 'pending']
function isValidStatus(value) {
return VALID_STATUSES.includes(value)
}
function setStatus(status) {
if (!isValidStatus(status)) {
throw new Error(`Invalid status: "${status}". Expected: ${VALID_STATUSES.join(', ')}`)
}
return status
}
try { setStatus('unknown') } catch (e) { console.log('Error:', e.message) }
console.log('Valid:', setStatus('active'))
// === 4. Безопасное приведение типов ===
console.log('\n=== 4. Type Conversion ===')
// TS: "hello" as unknown as number — double assertion
// В JS — явное преобразование
function safeToNumber(value) {
const num = Number(value)
if (isNaN(num)) {
console.warn(`Cannot convert "${value}" to number, using 0`)
return 0
}
return num
}
console.log(safeToNumber('42')) // 42
console.log(safeToNumber('abc')) // 0 + warning
console.log(safeToNumber(true)) // 1
console.log(safeToNumber(null)) // 0
// === 5. Circular references ===
console.log('\n=== 5. Circular References ===')
// Безопасная сериализация с циклическими ссылками
function safeStringify(obj) {
const seen = new WeakSet()
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]'
seen.add(value)
}
return value
})
}
const a = { name: 'a' }
const b = { name: 'b', ref: a }
a.ref = b // циклическая ссылка
console.log(safeStringify(a))
// === 6. Generic функции вместо any ===
console.log('\n=== 6. Generics vs any ===')
// TS: function identity<T>(x: T): T
function identity(x) { return x }
// TS: function first<T>(arr: T[]): T | undefined
function first(arr) { return arr[0] }
// TS: function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
function pick(obj, keys) {
return keys.reduce((result, key) => {
if (key in obj) result[key] = obj[key]
return result
}, {})
}
console.log(identity(42))
console.log(first([1, 2, 3]))
console.log(pick({ name: 'Алексей', email: 'a@b.com', role: 'admin' }, ['name', 'email']))
// === 7. Context (this) ===
console.log('\n=== 7. This Context ===')
class Timer {
constructor() { this.count = 0 }
startBroken() {
// TS: 'this' implicitly has type 'any' в обычных функциях
const fn = function() {
// this тут = undefined или globalThis, не Timer
}
return 'broken (this не указывает на Timer)'
}
startFixed() {
// Решение 1: стрелочная функция
const fn = () => {
this.count++ // this = Timer (замыкание)
}
fn()
return 'fixed with arrow function, count = ' + this.count
}
}
const timer = new Timer()
console.log(timer.startFixed())
// === 8. Exhaustive checks (switch) ===
console.log('\n=== 8. Exhaustive Switch ===')
// TS: function assertNever(x: never): never
function assertNever(value, allowedValues) {
throw new Error(`Unhandled value: "${value}". Expected one of: ${allowedValues.join(', ')}`)
}
function handleStatus(status) {
switch (status) {
case 'active': return 'Активен'
case 'inactive': return 'Неактивен'
case 'pending': return 'Ожидает'
default:
// В TS: assertNever(status) — ошибка компиляции если не все кейсы обработаны
return assertNever(status, ['active', 'inactive', 'pending'])
}
}
console.log(handleStatus('active'))
try { handleStatus('unknown') } catch (e) { console.log('Unhandled:', e.message) }
// === 9. Type-safe event system ===
console.log('\n=== 9. Type-safe Events ===')
function createTypedEmitter(validEvents) {
const listeners = {}
return {
on(event, handler) {
if (!validEvents.includes(event)) {
throw new Error(`Unknown event "${event}". Valid: ${validEvents.join(', ')}`)
}
if (!listeners[event]) listeners[event] = []
listeners[event].push(handler)
},
emit(event, data) {
if (!validEvents.includes(event)) {
throw new Error(`Unknown event: ${event}`)
}
;(listeners[event] || []).forEach(fn => fn(data))
}
}
}
const emitter = createTypedEmitter(['click', 'change', 'submit'])
emitter.on('click', data => console.log('click:', data))
emitter.emit('click', { x: 10, y: 20 })
try { emitter.on('unknown', () => {}) } catch (e) { console.log('Event error:', e.message) }
// === 10. Module не найден — runtime check ===
console.log('\n=== 10. Dynamic Import Guard ===')
async function safeImport(modulePath, fallback = null) {
try {
// В реальности: const mod = await import(modulePath)
// Симулируем:
if (modulePath === 'missing-module') throw new Error(`Cannot find module '${modulePath}'`)
return { default: 'loaded module' }
} catch (e) {
console.warn(`Module load failed: ${e.message}`)
return fallback
}
}
safeImport('missing-module').then(mod => {
console.log('Missing module result:', mod) // null
})Реализуй `createSafeObject(schema)` — фабрику объектов с безопасным доступом к свойствам. `schema` — объект с дефолтными значениями и типами `{ field: { default: any, type: "string"|"number"|"boolean" } }`. Возвращаемый объект: `get(key)` — возвращает значение или дефолт, `set(key, value)` — проверяет тип и устанавливает (или бросает ошибку), `has(key)` — проверяет наличие в schema, `toJSON()` — возвращает все значения.
В set(): store[key] = value. В has(): return key in schema. В toJSON(): return { ...store } — копия объекта. Chaining работает через return this в set().
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке