Шаблон Vue компилируется в **render function** — обычную JavaScript-функцию, которая возвращает VNode (виртуальный узел DOM). В большинстве случаев шаблоны удобнее, но render functions дают полную мощь JavaScript.
h() (от hyperscript) создаёт VNode:
import { h } from 'vue'
// h(tag, props, children)
const vnode = h('div', { class: 'container', id: 'app' }, [
h('h1', null, 'Заголовок'),
h('p', { style: 'color: red' }, 'Текст'),
])import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
// Возвращаем функцию — это render function
return () => h('div', [
h('p', `Счётчик: ${count.value}`),
h('button', { onClick: () => count.value++ }, '+'),
])
}
}import MyButton from './MyButton.vue'
// h() принимает компоненты напрямую
h(MyButton, {
label: 'Нажми',
variant: 'primary',
onClick: handleClick,
})
// Слоты — третий аргумент как объект
h(MyLayout, {}, {
default: () => h('p', 'Основной контент'),
header: () => h('h1', 'Заголовок'),
footer: () => h('footer', 'Подвал'),
})// Функциональный компонент — просто функция без состояния
const FancyHeading = (props, { slots }) => {
const level = props.level || 2
return h(`h${level}`, { class: 'fancy' }, slots.default?.())
}
// Использование в h()
h(FancyHeading, { level: 3 }, { default: () => 'Заголовок' })Vue поддерживает JSX как альтернативу h():
// MyComponent.vue с lang="tsx"
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => (
<div class="container">
<p>Счётчик: {count.value}</p>
<button onClick={() => count.value++}>+</button>
</div>
)
}
}Используйте render functions когда:
Оставайтесь на шаблонах когда:
const vnode = h('p', { id: 'text' }, 'Привет')
// Это обычный объект:
// {
// type: 'p',
// props: { id: 'text' },
// children: 'Привет',
// el: null, // реальный DOM-элемент после монтирования
// key: null,
// ...
// }VNode — лёгкий JavaScript-объект. Vue сравнивает деревья VNode при обновлении (Virtual DOM diffing) и точечно обновляет реальный DOM.
Реализация упрощённого h() и Virtual DOM — строим и рендерим дерево VNode в HTML-строку
// Реализуем упрощённый h() и рендерер VNode → HTML.
// Именно так работает Vue под капотом.
// --- h() — создание VNode ---
function h(type, props, children) {
return {
type,
props: props || {},
children: normalizeChildren(children),
_isVNode: true,
}
}
function normalizeChildren(children) {
if (children == null) return []
if (!Array.isArray(children)) return [children]
return children.flat()
}
// --- Рендерер VNode → HTML строка ---
function renderToString(vnode) {
if (vnode == null || vnode === false) return ''
if (typeof vnode === 'string' || typeof vnode === 'number') {
return String(vnode)
}
const { type, props, children } = vnode
// Функциональный компонент
if (typeof type === 'function') {
const rendered = type(props, { slots: {} })
return renderToString(rendered)
}
// HTML-атрибуты
const attrs = Object.entries(props)
.filter(([k]) => !k.startsWith('on') && k !== 'key') // игнорируем обработчики
.map(([k, v]) => {
if (k === 'className') k = 'class'
if (typeof v === 'boolean') return v ? k : ''
return `${k}="${v}"`
})
.filter(Boolean)
.join(' ')
const attrsStr = attrs ? ' ' + attrs : ''
// Самозакрывающиеся теги
const selfClosing = ['br', 'hr', 'img', 'input', 'link', 'meta']
if (selfClosing.includes(type)) {
return `<${type}${attrsStr} />`
}
const innerHtml = children.map(renderToString).join('')
return `<${type}${attrsStr}>${innerHtml}</${type}>`
}
// --- Функциональный компонент (аналог Vue functional component) ---
const FancyHeading = (props) => {
const level = props.level || 2
const text = props.text || ''
return h(`h${level}`, { class: 'fancy', id: props.id }, text)
}
// Компонент рекурсивного дерева
function TreeNode(props) {
const { node } = props
if (!node.children || node.children.length === 0) {
return h('li', { class: 'leaf' }, node.label)
}
return h('li', {}, [
h('span', { class: 'node' }, node.label),
h('ul', {},
node.children.map(child => TreeNode({ node: child }))
)
])
}
// --- Примеры ---
console.log('=== Базовые VNode ===')
const vdom = h('div', { class: 'container', id: 'app' }, [
h('h1', { class: 'title' }, 'Привет, Vue!'),
h('p', { style: 'color: blue' }, 'Параграф'),
h('ul', {}, [
h('li', null, 'Один'),
h('li', null, 'Два'),
h('li', null, 'Три'),
]),
h('br', null),
h('input', { type: 'text', placeholder: 'Введите текст' }),
])
console.log(renderToString(vdom))
console.log('\n=== Функциональный компонент ===')
const headings = h('div', {}, [
h(FancyHeading, { level: 1, text: 'Заголовок 1', id: 'h1' }),
h(FancyHeading, { level: 2, text: 'Заголовок 2' }),
h(FancyHeading, { level: 3, text: 'Заголовок 3' }),
])
console.log(renderToString(headings))
console.log('\n=== Рекурсивное дерево ===')
const tree = {
label: 'Корень',
children: [
{ label: 'Узел A', children: [
{ label: 'Лист A1', children: [] },
{ label: 'Лист A2', children: [] },
]},
{ label: 'Узел B', children: [
{ label: 'Лист B1', children: [] },
]},
]
}
const treeVdom = h('ul', { class: 'tree' }, [TreeNode({ node: tree })])
console.log(renderToString(treeVdom))
// Сравниваем два VNode (упрощённый diff)
console.log('\n=== VNode структура ===')
const vnode = h('p', { id: 'test' }, 'Текст')
console.log(JSON.stringify(vnode, null, 2))
Шаблон Vue компилируется в **render function** — обычную JavaScript-функцию, которая возвращает VNode (виртуальный узел DOM). В большинстве случаев шаблоны удобнее, но render functions дают полную мощь JavaScript.
h() (от hyperscript) создаёт VNode:
import { h } from 'vue'
// h(tag, props, children)
const vnode = h('div', { class: 'container', id: 'app' }, [
h('h1', null, 'Заголовок'),
h('p', { style: 'color: red' }, 'Текст'),
])import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
// Возвращаем функцию — это render function
return () => h('div', [
h('p', `Счётчик: ${count.value}`),
h('button', { onClick: () => count.value++ }, '+'),
])
}
}import MyButton from './MyButton.vue'
// h() принимает компоненты напрямую
h(MyButton, {
label: 'Нажми',
variant: 'primary',
onClick: handleClick,
})
// Слоты — третий аргумент как объект
h(MyLayout, {}, {
default: () => h('p', 'Основной контент'),
header: () => h('h1', 'Заголовок'),
footer: () => h('footer', 'Подвал'),
})// Функциональный компонент — просто функция без состояния
const FancyHeading = (props, { slots }) => {
const level = props.level || 2
return h(`h${level}`, { class: 'fancy' }, slots.default?.())
}
// Использование в h()
h(FancyHeading, { level: 3 }, { default: () => 'Заголовок' })Vue поддерживает JSX как альтернативу h():
// MyComponent.vue с lang="tsx"
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => (
<div class="container">
<p>Счётчик: {count.value}</p>
<button onClick={() => count.value++}>+</button>
</div>
)
}
}Используйте render functions когда:
Оставайтесь на шаблонах когда:
const vnode = h('p', { id: 'text' }, 'Привет')
// Это обычный объект:
// {
// type: 'p',
// props: { id: 'text' },
// children: 'Привет',
// el: null, // реальный DOM-элемент после монтирования
// key: null,
// ...
// }VNode — лёгкий JavaScript-объект. Vue сравнивает деревья VNode при обновлении (Virtual DOM diffing) и точечно обновляет реальный DOM.
Реализация упрощённого h() и Virtual DOM — строим и рендерим дерево VNode в HTML-строку
// Реализуем упрощённый h() и рендерер VNode → HTML.
// Именно так работает Vue под капотом.
// --- h() — создание VNode ---
function h(type, props, children) {
return {
type,
props: props || {},
children: normalizeChildren(children),
_isVNode: true,
}
}
function normalizeChildren(children) {
if (children == null) return []
if (!Array.isArray(children)) return [children]
return children.flat()
}
// --- Рендерер VNode → HTML строка ---
function renderToString(vnode) {
if (vnode == null || vnode === false) return ''
if (typeof vnode === 'string' || typeof vnode === 'number') {
return String(vnode)
}
const { type, props, children } = vnode
// Функциональный компонент
if (typeof type === 'function') {
const rendered = type(props, { slots: {} })
return renderToString(rendered)
}
// HTML-атрибуты
const attrs = Object.entries(props)
.filter(([k]) => !k.startsWith('on') && k !== 'key') // игнорируем обработчики
.map(([k, v]) => {
if (k === 'className') k = 'class'
if (typeof v === 'boolean') return v ? k : ''
return `${k}="${v}"`
})
.filter(Boolean)
.join(' ')
const attrsStr = attrs ? ' ' + attrs : ''
// Самозакрывающиеся теги
const selfClosing = ['br', 'hr', 'img', 'input', 'link', 'meta']
if (selfClosing.includes(type)) {
return `<${type}${attrsStr} />`
}
const innerHtml = children.map(renderToString).join('')
return `<${type}${attrsStr}>${innerHtml}</${type}>`
}
// --- Функциональный компонент (аналог Vue functional component) ---
const FancyHeading = (props) => {
const level = props.level || 2
const text = props.text || ''
return h(`h${level}`, { class: 'fancy', id: props.id }, text)
}
// Компонент рекурсивного дерева
function TreeNode(props) {
const { node } = props
if (!node.children || node.children.length === 0) {
return h('li', { class: 'leaf' }, node.label)
}
return h('li', {}, [
h('span', { class: 'node' }, node.label),
h('ul', {},
node.children.map(child => TreeNode({ node: child }))
)
])
}
// --- Примеры ---
console.log('=== Базовые VNode ===')
const vdom = h('div', { class: 'container', id: 'app' }, [
h('h1', { class: 'title' }, 'Привет, Vue!'),
h('p', { style: 'color: blue' }, 'Параграф'),
h('ul', {}, [
h('li', null, 'Один'),
h('li', null, 'Два'),
h('li', null, 'Три'),
]),
h('br', null),
h('input', { type: 'text', placeholder: 'Введите текст' }),
])
console.log(renderToString(vdom))
console.log('\n=== Функциональный компонент ===')
const headings = h('div', {}, [
h(FancyHeading, { level: 1, text: 'Заголовок 1', id: 'h1' }),
h(FancyHeading, { level: 2, text: 'Заголовок 2' }),
h(FancyHeading, { level: 3, text: 'Заголовок 3' }),
])
console.log(renderToString(headings))
console.log('\n=== Рекурсивное дерево ===')
const tree = {
label: 'Корень',
children: [
{ label: 'Узел A', children: [
{ label: 'Лист A1', children: [] },
{ label: 'Лист A2', children: [] },
]},
{ label: 'Узел B', children: [
{ label: 'Лист B1', children: [] },
]},
]
}
const treeVdom = h('ul', { class: 'tree' }, [TreeNode({ node: tree })])
console.log(renderToString(treeVdom))
// Сравниваем два VNode (упрощённый diff)
console.log('\n=== VNode структура ===')
const vnode = h('p', { id: 'test' }, 'Текст')
console.log(JSON.stringify(vnode, null, 2))
Реализуй функцию `h(type, props, children)` создающую VNode-объект, и функцию `renderToHTML(vnode)` преобразующую VNode в HTML-строку. renderToHTML должна: обрабатывать строки/числа напрямую (возвращать как строку), поддерживать props как атрибуты HTML (обработчики onClick/onXxx — игнорировать), вложенные дочерние узлы — рекурсивно. Самозакрывающиеся теги: br, hr, img, input.
В h() для нормализации children: if (children == null) return []; if (!Array.isArray(children)) return [children]; return children.flat(). В renderToHTML фильтруй атрибуты: .filter(([k]) => !k.startsWith("on")). Замени className на class через k === "className" ? "class" : k. Самозакрывающиеся: const selfClose = ["br","hr","img","input"]; if (selfClose.includes(type)) return `<${type}${attrs} />`.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке