Компонент — это переиспользуемый, самодостаточный блок интерфейса. В Vue 3 каждый компонент объединяет шаблон, логику и стили. Компоненты позволяют разбить сложный UI на маленькие, понятные части.
defineComponent() — вспомогательная функция Vue 3 для определения компонента с поддержкой TypeScript-вывода типов:
// Button.vue (упрощённо — SFC)
import { defineComponent } from 'vue'
const Button = defineComponent({
name: 'Button',
props: {
label: String,
disabled: Boolean,
},
setup(props) {
return () => `<button disabled=${props.disabled}>${props.label}</button>`
}
})Props — это параметры, которые родитель передаёт дочернему компоненту. Данные текут **только сверху вниз** (однонаправленный поток).
defineComponent({
props: {
// Просто тип
title: String,
// Объект с опциями
count: {
type: Number,
required: true, // обязателен
},
status: {
type: String,
default: 'active', // значение по умолчанию
validator(value) { // кастомная валидация
return ['active', 'inactive', 'pending'].includes(value)
}
},
// Несколько допустимых типов
id: [String, Number],
}
})Props идут от родителя к ребёнку. Дочерний компонент **не должен изменять** props напрямую — это нарушение однонаправленного потока:
setup(props) {
// НЕПРАВИЛЬНО — мутировать props
// props.count = 10
// ПРАВИЛЬНО — создать локальное состояние на основе props
const localCount = ref(props.count)
return { localCount }
}props: {
message: {
type: String,
required: true, // Vue выдаст предупреждение если не передан
},
theme: {
type: String,
default: 'light', // используется если prop не передан
},
items: {
type: Array,
default: () => [], // для объектов/массивов — функция-фабрика!
}
}<!-- Родительский шаблон -->
<template>
<Button label="Нажми меня" :disabled="false" />
<UserCard :user="currentUser" status="active" />
</template>Паттерн компонентов через factory functions с валидацией props
// Аналог defineComponent через factory function
function defineComponent({ name, props: propsSchema, setup }) {
return function createInstance(props) {
// Валидация props
for (const [key, schema] of Object.entries(propsSchema)) {
const value = props[key]
const def = typeof schema === 'function' ? { type: schema } : schema
// Проверка required
if (def.required && (value === undefined || value === null)) {
console.warn(`[Component: ${name}] Prop "${key}" is required but not provided`)
continue
}
// Если значение не передано — применить default
if (value === undefined && 'default' in def) {
props[key] = typeof def.default === 'function' ? def.default() : def.default
}
// Проверка типа
if (value !== undefined && def.type) {
const expectedType = def.type.name
const actualType = Array.isArray(value) ? 'Array' : typeof value
const typeMatch =
actualType === expectedType.toLowerCase() ||
value instanceof def.type
if (!typeMatch) {
console.warn(`[Component: ${name}] Prop "${key}": expected ${expectedType}, got ${actualType}`)
}
}
// Кастомный validator
if (value !== undefined && def.validator) {
if (!def.validator(value)) {
console.warn(`[Component: ${name}] Prop "${key}": validator failed for value "${value}"`)
}
}
}
// Запускаем setup с провалидированными props
return setup(props)
}
}
// Создаём компонент StatusBadge
const StatusBadge = defineComponent({
name: 'StatusBadge',
props: {
label: {
type: String,
required: true,
},
status: {
type: String,
default: 'active',
validator: (v) => ['active', 'inactive', 'pending'].includes(v),
},
count: {
type: Number,
default: 0,
},
},
setup(props) {
return {
render() {
return `[${props.status.toUpperCase()}] ${props.label} (x${props.count})`
}
}
}
})
// Использование
const badge1 = StatusBadge({ label: 'Tasks', status: 'active', count: 5 })
console.log(badge1.render())
// [ACTIVE] Tasks (x5)
const badge2 = StatusBadge({ label: 'Messages' }) // defaults применятся
console.log(badge2.render())
// [ACTIVE] Messages (x0)
// Невалидный статус — будет предупреждение
const badge3 = StatusBadge({ label: 'Alerts', status: 'broken', count: 1 })
console.log(badge3.render())Компонент — это переиспользуемый, самодостаточный блок интерфейса. В Vue 3 каждый компонент объединяет шаблон, логику и стили. Компоненты позволяют разбить сложный UI на маленькие, понятные части.
defineComponent() — вспомогательная функция Vue 3 для определения компонента с поддержкой TypeScript-вывода типов:
// Button.vue (упрощённо — SFC)
import { defineComponent } from 'vue'
const Button = defineComponent({
name: 'Button',
props: {
label: String,
disabled: Boolean,
},
setup(props) {
return () => `<button disabled=${props.disabled}>${props.label}</button>`
}
})Props — это параметры, которые родитель передаёт дочернему компоненту. Данные текут **только сверху вниз** (однонаправленный поток).
defineComponent({
props: {
// Просто тип
title: String,
// Объект с опциями
count: {
type: Number,
required: true, // обязателен
},
status: {
type: String,
default: 'active', // значение по умолчанию
validator(value) { // кастомная валидация
return ['active', 'inactive', 'pending'].includes(value)
}
},
// Несколько допустимых типов
id: [String, Number],
}
})Props идут от родителя к ребёнку. Дочерний компонент **не должен изменять** props напрямую — это нарушение однонаправленного потока:
setup(props) {
// НЕПРАВИЛЬНО — мутировать props
// props.count = 10
// ПРАВИЛЬНО — создать локальное состояние на основе props
const localCount = ref(props.count)
return { localCount }
}props: {
message: {
type: String,
required: true, // Vue выдаст предупреждение если не передан
},
theme: {
type: String,
default: 'light', // используется если prop не передан
},
items: {
type: Array,
default: () => [], // для объектов/массивов — функция-фабрика!
}
}<!-- Родительский шаблон -->
<template>
<Button label="Нажми меня" :disabled="false" />
<UserCard :user="currentUser" status="active" />
</template>Паттерн компонентов через factory functions с валидацией props
// Аналог defineComponent через factory function
function defineComponent({ name, props: propsSchema, setup }) {
return function createInstance(props) {
// Валидация props
for (const [key, schema] of Object.entries(propsSchema)) {
const value = props[key]
const def = typeof schema === 'function' ? { type: schema } : schema
// Проверка required
if (def.required && (value === undefined || value === null)) {
console.warn(`[Component: ${name}] Prop "${key}" is required but not provided`)
continue
}
// Если значение не передано — применить default
if (value === undefined && 'default' in def) {
props[key] = typeof def.default === 'function' ? def.default() : def.default
}
// Проверка типа
if (value !== undefined && def.type) {
const expectedType = def.type.name
const actualType = Array.isArray(value) ? 'Array' : typeof value
const typeMatch =
actualType === expectedType.toLowerCase() ||
value instanceof def.type
if (!typeMatch) {
console.warn(`[Component: ${name}] Prop "${key}": expected ${expectedType}, got ${actualType}`)
}
}
// Кастомный validator
if (value !== undefined && def.validator) {
if (!def.validator(value)) {
console.warn(`[Component: ${name}] Prop "${key}": validator failed for value "${value}"`)
}
}
}
// Запускаем setup с провалидированными props
return setup(props)
}
}
// Создаём компонент StatusBadge
const StatusBadge = defineComponent({
name: 'StatusBadge',
props: {
label: {
type: String,
required: true,
},
status: {
type: String,
default: 'active',
validator: (v) => ['active', 'inactive', 'pending'].includes(v),
},
count: {
type: Number,
default: 0,
},
},
setup(props) {
return {
render() {
return `[${props.status.toUpperCase()}] ${props.label} (x${props.count})`
}
}
}
})
// Использование
const badge1 = StatusBadge({ label: 'Tasks', status: 'active', count: 5 })
console.log(badge1.render())
// [ACTIVE] Tasks (x5)
const badge2 = StatusBadge({ label: 'Messages' }) // defaults применятся
console.log(badge2.render())
// [ACTIVE] Messages (x0)
// Невалидный статус — будет предупреждение
const badge3 = StatusBadge({ label: 'Alerts', status: 'broken', count: 1 })
console.log(badge3.render())Реализуй функцию `createComponent({ props, setup })`, которая принимает схему props и функцию setup, и возвращает factory-функцию для создания экземпляров компонента. Требования: - Props схема: каждый prop может быть `{ type, required, default, validator }` - Если required prop не передан — выбросить `Error` (не warn, а именно Error) - Если prop не передан и есть default — применить его (для функций вызвать как фабрику) - Если передан validator и он вернул false — выбросить Error - setup получает провалидированные props и должна вернуть объект (экземпляр компонента) Пример использования: ``` const MyComp = createComponent({ props: { name: { type: 'string', required: true }, role: { type: 'string', default: 'user', validator: v => ['user','admin'].includes(v) }, }, setup(props) { return { greet: () => `Hello, ${props.role}: ${props.name}` } } }) const instance = MyComp({ name: 'Alice', role: 'admin' }) console.log(instance.greet()) // Hello, admin: Alice ```
Используй for...of по Object.entries(propsSchema). Для default: проверь typeof def.default === 'function'. Для required: проверь value === undefined. Для validator: вызови def.validator(value) и если false — throw new Error.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке