TypeScript добавляет статическую типизацию, автодополнение и раннее обнаружение ошибок. Vue 3 написан на TypeScript и имеет первоклассную поддержку — типы встроены в пакет vue.
<script setup lang="ts">
import { ref, computed } from 'vue'
// ref автоматически выводит тип из начального значения
const count = ref(0) // Ref<number>
const name = ref('Иван') // Ref<string>
const items = ref<string[]>([]) // Ref<string[]> — явная аннотация
// computed тоже выводится автоматически
const doubled = computed(() => count.value * 2) // ComputedRef<number>
// Явная аннотация через дженерик
const user = ref<User | null>(null)
</script>// Через TypeScript интерфейс — рекомендуемый способ
interface Props {
title: string
count?: number
items: string[]
callback: (id: number) => void
}
const props = defineProps<Props>()
// С дефолтными значениями:
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [],
})// Типизированные события
const emit = defineEmits<{
change: [value: string] // payload — строка
submit: [data: FormData] // payload — объект
'update:modelValue': [val: number]
close: [] // без payload
}>()
// Использование
emit('change', 'новое значение')
emit('submit', formData)
emit('close')import { reactive } from 'vue'
interface UserState {
name: string
age: number
email: string | null
}
// reactive выводит тип автоматически, но лучше явно:
const state = reactive<UserState>({
name: 'Иван',
age: 25,
email: null,
})import { ref, onMounted } from 'vue'
// Шаблонная ссылка на DOM-элемент
const inputEl = ref<HTMLInputElement | null>(null)
const divEl = ref<HTMLDivElement | null>(null)
onMounted(() => {
inputEl.value?.focus() // optional chaining — el может быть null
})// useCounter.ts
import { ref, Ref } from 'vue'
interface UseCounterReturn {
count: Ref<number>
increment: () => void
reset: (value?: number) => void
}
export function useCounter(initial = 0): UseCounterReturn {
const count = ref(initial)
const increment = () => count.value++
const reset = (value = 0) => { count.value = value }
return { count, increment, reset }
}import type { Component } from 'vue'
const components: Record<string, Component> = {
home: HomeView,
about: AboutView,
}
// defineComponent для Options API с типами
import { defineComponent } from 'vue'
export default defineComponent({
props: { title: String },
setup(props) {
// props.title — string | undefined
}
})Система типизированных событий — аналог defineEmits<T> — с проверкой сигнатур в рантайме
// Эмулируем систему типизированных событий Vue:
// defineEmits<T>() проверяет типы событий в TypeScript.
// В JS-рантайме реализуем схожую валидацию через схему.
// --- Типизированный EventEmitter ---
function createTypedEmitter(schema) {
// schema: { eventName: { validate: fn } }
const listeners = {}
return {
// Подписаться на событие (аналог @change="handler")
on(event, handler) {
if (!schema[event]) {
throw new Error(`Событие "${event}" не объявлено в emit-схеме`)
}
if (!listeners[event]) listeners[event] = []
listeners[event].push(handler)
return () => { // возвращаем функцию отписки
listeners[event] = listeners[event].filter(h => h !== handler)
}
},
// Вызвать событие (аналог emit('change', value))
emit(event, ...args) {
const def = schema[event]
if (!def) throw new Error(`Событие "${event}" не объявлено`)
// Runtime валидация (в TS это делается на этапе компиляции)
if (def.validate) {
const error = def.validate(...args)
if (error) throw new TypeError(`emit("${event}"): ${error}`)
}
;(listeners[event] || []).forEach(h => h(...args))
return true
},
// Список доступных событий (аналог интерфейса defineEmits<T>)
getSchema() { return Object.keys(schema) }
}
}
// --- Схема событий компонента UserForm ---
// Аналог: defineEmits<{
// 'update:name': [value: string]
// 'update:age': [value: number]
// submit: [data: { name: string, age: number }]
// close: []
// }>()
const formEmits = createTypedEmitter({
'update:name': {
validate: (val) => {
if (typeof val !== 'string') return `ожидается string, получено ${typeof val}`
if (val.length === 0) return 'имя не может быть пустым'
}
},
'update:age': {
validate: (val) => {
if (typeof val !== 'number') return `ожидается number, получено ${typeof val}`
if (val < 0 || val > 150) return `возраст вне диапазона: ${val}`
}
},
submit: {
validate: (data) => {
if (!data || typeof data !== 'object') return 'ожидается объект'
if (!data.name || !data.age) return 'объект должен содержать name и age'
}
},
close: {
validate: () => undefined // нет аргументов
},
})
// --- "Родительский" компонент — подписывается на события ---
const unsubName = formEmits.on('update:name', (val) => {
console.log('[Parent] update:name →', val)
})
formEmits.on('update:age', (val) => {
console.log('[Parent] update:age →', val)
})
formEmits.on('submit', (data) => {
console.log('[Parent] submit →', JSON.stringify(data))
})
formEmits.on('close', () => {
console.log('[Parent] close')
})
// --- "Дочерний" компонент — эмитирует события ---
console.log('=== Доступные события:', formEmits.getSchema())
console.log('\n=== Корректные emit-ы ===')
formEmits.emit('update:name', 'Иван')
formEmits.emit('update:age', 25)
formEmits.emit('submit', { name: 'Иван', age: 25 })
formEmits.emit('close')
console.log('\n=== Ошибки валидации ===')
try { formEmits.emit('update:name', 123) } catch(e) { console.error(e.message) }
try { formEmits.emit('update:age', -5) } catch(e) { console.error(e.message) }
try { formEmits.emit('submit', 'строка') } catch(e) { console.error(e.message) }
try { formEmits.emit('unknownEvent') } catch(e) { console.error(e.message) }
try { formEmits.on('nonExistent', () => {}) } catch(e) { console.error(e.message) }
// Отписка
unsubName()
console.log('\n=== После отписки от update:name ===')
formEmits.emit('update:name', 'Пётр') // обработчик не вызовется
console.log('(update:name не отработал)')
TypeScript добавляет статическую типизацию, автодополнение и раннее обнаружение ошибок. Vue 3 написан на TypeScript и имеет первоклассную поддержку — типы встроены в пакет vue.
<script setup lang="ts">
import { ref, computed } from 'vue'
// ref автоматически выводит тип из начального значения
const count = ref(0) // Ref<number>
const name = ref('Иван') // Ref<string>
const items = ref<string[]>([]) // Ref<string[]> — явная аннотация
// computed тоже выводится автоматически
const doubled = computed(() => count.value * 2) // ComputedRef<number>
// Явная аннотация через дженерик
const user = ref<User | null>(null)
</script>// Через TypeScript интерфейс — рекомендуемый способ
interface Props {
title: string
count?: number
items: string[]
callback: (id: number) => void
}
const props = defineProps<Props>()
// С дефолтными значениями:
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [],
})// Типизированные события
const emit = defineEmits<{
change: [value: string] // payload — строка
submit: [data: FormData] // payload — объект
'update:modelValue': [val: number]
close: [] // без payload
}>()
// Использование
emit('change', 'новое значение')
emit('submit', formData)
emit('close')import { reactive } from 'vue'
interface UserState {
name: string
age: number
email: string | null
}
// reactive выводит тип автоматически, но лучше явно:
const state = reactive<UserState>({
name: 'Иван',
age: 25,
email: null,
})import { ref, onMounted } from 'vue'
// Шаблонная ссылка на DOM-элемент
const inputEl = ref<HTMLInputElement | null>(null)
const divEl = ref<HTMLDivElement | null>(null)
onMounted(() => {
inputEl.value?.focus() // optional chaining — el может быть null
})// useCounter.ts
import { ref, Ref } from 'vue'
interface UseCounterReturn {
count: Ref<number>
increment: () => void
reset: (value?: number) => void
}
export function useCounter(initial = 0): UseCounterReturn {
const count = ref(initial)
const increment = () => count.value++
const reset = (value = 0) => { count.value = value }
return { count, increment, reset }
}import type { Component } from 'vue'
const components: Record<string, Component> = {
home: HomeView,
about: AboutView,
}
// defineComponent для Options API с типами
import { defineComponent } from 'vue'
export default defineComponent({
props: { title: String },
setup(props) {
// props.title — string | undefined
}
})Система типизированных событий — аналог defineEmits<T> — с проверкой сигнатур в рантайме
// Эмулируем систему типизированных событий Vue:
// defineEmits<T>() проверяет типы событий в TypeScript.
// В JS-рантайме реализуем схожую валидацию через схему.
// --- Типизированный EventEmitter ---
function createTypedEmitter(schema) {
// schema: { eventName: { validate: fn } }
const listeners = {}
return {
// Подписаться на событие (аналог @change="handler")
on(event, handler) {
if (!schema[event]) {
throw new Error(`Событие "${event}" не объявлено в emit-схеме`)
}
if (!listeners[event]) listeners[event] = []
listeners[event].push(handler)
return () => { // возвращаем функцию отписки
listeners[event] = listeners[event].filter(h => h !== handler)
}
},
// Вызвать событие (аналог emit('change', value))
emit(event, ...args) {
const def = schema[event]
if (!def) throw new Error(`Событие "${event}" не объявлено`)
// Runtime валидация (в TS это делается на этапе компиляции)
if (def.validate) {
const error = def.validate(...args)
if (error) throw new TypeError(`emit("${event}"): ${error}`)
}
;(listeners[event] || []).forEach(h => h(...args))
return true
},
// Список доступных событий (аналог интерфейса defineEmits<T>)
getSchema() { return Object.keys(schema) }
}
}
// --- Схема событий компонента UserForm ---
// Аналог: defineEmits<{
// 'update:name': [value: string]
// 'update:age': [value: number]
// submit: [data: { name: string, age: number }]
// close: []
// }>()
const formEmits = createTypedEmitter({
'update:name': {
validate: (val) => {
if (typeof val !== 'string') return `ожидается string, получено ${typeof val}`
if (val.length === 0) return 'имя не может быть пустым'
}
},
'update:age': {
validate: (val) => {
if (typeof val !== 'number') return `ожидается number, получено ${typeof val}`
if (val < 0 || val > 150) return `возраст вне диапазона: ${val}`
}
},
submit: {
validate: (data) => {
if (!data || typeof data !== 'object') return 'ожидается объект'
if (!data.name || !data.age) return 'объект должен содержать name и age'
}
},
close: {
validate: () => undefined // нет аргументов
},
})
// --- "Родительский" компонент — подписывается на события ---
const unsubName = formEmits.on('update:name', (val) => {
console.log('[Parent] update:name →', val)
})
formEmits.on('update:age', (val) => {
console.log('[Parent] update:age →', val)
})
formEmits.on('submit', (data) => {
console.log('[Parent] submit →', JSON.stringify(data))
})
formEmits.on('close', () => {
console.log('[Parent] close')
})
// --- "Дочерний" компонент — эмитирует события ---
console.log('=== Доступные события:', formEmits.getSchema())
console.log('\n=== Корректные emit-ы ===')
formEmits.emit('update:name', 'Иван')
formEmits.emit('update:age', 25)
formEmits.emit('submit', { name: 'Иван', age: 25 })
formEmits.emit('close')
console.log('\n=== Ошибки валидации ===')
try { formEmits.emit('update:name', 123) } catch(e) { console.error(e.message) }
try { formEmits.emit('update:age', -5) } catch(e) { console.error(e.message) }
try { formEmits.emit('submit', 'строка') } catch(e) { console.error(e.message) }
try { formEmits.emit('unknownEvent') } catch(e) { console.error(e.message) }
try { formEmits.on('nonExistent', () => {}) } catch(e) { console.error(e.message) }
// Отписка
unsubName()
console.log('\n=== После отписки от update:name ===')
formEmits.emit('update:name', 'Пётр') // обработчик не вызовется
console.log('(update:name не отработал)')
Реализуй функцию `createReactiveStore(initialState)`, которая создаёт типобезопасное реактивное хранилище. Метод `get(key)` возвращает текущее значение поля. Метод `set(key, value)` обновляет значение; если тип нового значения не совпадает с типом начального — выбрасывает TypeError. Метод `subscribe(key, callback)` подписывается на изменения конкретного поля, возвращает функцию отписки. Метод `getState()` возвращает копию всего состояния.
Для инициализации _types перебери ключи initialState: for (const key in initialState) { store._types[key] = typeof initialState[key] }. В set проверяй: if (typeof value !== this._types[key]) throw new TypeError(`...`). В subscribe: if (!this._listeners[key]) this._listeners[key] = []; this._listeners[key].push(callback); return () => { this._listeners[key] = this._listeners[key].filter(cb => cb !== callback) }.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке