vue组件为什么只能有一个根元素?template模板是如何渲染出真实DOM
1.vue2中组件只能有一个根节点
Vue2中 组件只能有一个根元素,是因为Vue的模板编译器在编译模板时,会将模板转换成一个render函数,而这个render函数只能返回一个单一的根节点。
如果组件有多个根节点,则无法满足这个要求。例如:
<template>
<div class="container">
<h1>{{ title }}</h1>
<p>{{ content }}</p>
</div>
<button @click="submit">Submit</button>
</template>
在编译时,会报错:
Template compilation error: Component template should contain exactly one root element.
因此,在Vue中,每个组件必须有且只能有一个根元素。如果需要多个元素并列显示,可以使用一个父元素将它们包裹起来。例如:
<template>
<div class="container">
<h1>{{ title }}</h1>
<p>{{ content }}</p>
<button @click="submit">Submit</button>
</div>
</template>
这样就可以满足单一根节点的要求,并且不影响页面布局和样式。
2.vue3可以有多个根节点
Vue 3.x 中支持多个根元素,这是因为 Vue 3.x 中使用了 Fragment(片段)的概念,即可以使用 <template>
标签或 Fragment
组件来包裹多个根元素。
在 Vue 2.x 中,由于模板编译器的限制,每个组件必须有且只能有一个根元素。这是因为 Vue 2.x 的模板编译器会将模板转换成一个 render 函数,并且这个函数需要返回一个单一的根节点。
而在 Vue 3.x 中,采用了基于函数式编程的渲染方式,不再需要将模板转换成 render 函数。相反,Vue 3.x 使用了基于模板的编译方式,并且支持了 Fragment 的概念。Fragment 是一种虚拟节点,它可以包含多个子节点,并且不会在 DOM 树中创建任何实际的元素。
例如,在 Vue 3.x 中可以这样写:
<template>
<div>
<h1>Header</h1>
<p>Content</p>
</div>
<div>
<h1>Header</h1>
<p>Content</p>
</div>
</template>
或者使用 Fragment:
<template>
<>
<div>
<h1>Header</h1>
<p>Content</p>
</div>
<div>
<h1>Header</h1>
<p>Content</p>
</div>
</>
</template>
<script setup>
import { defineComponent } from 'vue'
export default defineComponent({
})
</script>
这样就可以实现多个根元素了。
3.render函数封装有什么特别的,或者用到比较巧妙的东西吗?
使用render函数封装组件可以让我们更加灵活地控制组件的渲染,同时也可以提高组件的性能(因为vue中会先把template的内容转化成render函数)。在render函数的封装中,有一些比较巧妙的技巧和特点。
- 使用JSX语法
JSX是一种JavaScript语法扩展,可以在JavaScript中编写类似HTML的代码。使用JSX语法可以使render函数更加直观和易读。
例如,在Vue中使用JSX语法封装一个HelloWorld组件:
const HelloWorld = {
render() {
return (
<div>
<h1>Hello World</h1>
<p>This is a JSX component</p>
</div>
)
}
}
- 使用函数式组件
函数式组件是指没有状态(data)和实例(this)的组件,只接受props作为参数并返回一个虚拟节点。使用函数式组件可以提高渲染性能,并且更加容易进行单元测试。
例如,在Vue中使用函数式组件封装一个HelloWorld组件:
const HelloWorld = {
functional: true,
props: {
message: String
},
render(h, { props }) {
return h('div', [
h('h1', 'Hello World'),
h('p', props.message)
])
}
}
- 使用插槽(slot)
插槽是一种特殊的虚拟节点,用于在父组件中传递子组件内容。使用插槽可以使render函数更加灵活和可复用。
例如,在Vue中使用插槽封装一个ButtonGroup组件:
const ButtonGroup = {
functional: true,
render(h, { slots }) {
return h('div', { class: 'button-group' }, slots.default())
}
}
const App = {
render() {
return (
<ButtonGroup>
<button>Button A</button>
<button>Button B</button>
<button>Button C</button>
</ButtonGroup>
)
}
}
- 使用动态属性
动态属性是指根据props或其他变量动态生成属性值。使用动态属性可以使render函数更加灵活和可配置。
例如,在Vue中使用动态属性封装一个Link组件:
const Link = {
functional: true,
props: {
to: String
},
render(h, { props, children }) {
return h('a', { href: props.to }, children)
}
}
const App = {
data() {
return { url: 'https://www.example.com' }
},
render() {
return (
<Link to={this.url}>Click me!</Link>
)
}
}
总之,render函数封装有很多特别的技巧和特点,通过灵活运用这些技巧和特点,我们可以更好地掌控Vue的渲染过程,并提高应用程序的性能和可维护性。
3.1. Vue2 render函数创建组件
在Vue2中,可以使用渲染函数来代替模板来定义组件的视图。渲染函数是一个JavaScript函数,它返回一个虚拟DOM节点,用于描述组件的结构和内容。
以下是一个简单的示例,展示了如何使用渲染函数来创建一个简单的HelloWorld组件:
Vue.component('HelloWorld', {
render: function (createElement) {
return createElement('div', {class: 'container'}, 'Hello World');
}
});
在上面的例子中,我们定义了一个名为HelloWorld的组件,并在其render方法中返回了一个div元素节点,内容为"Hello World"
需要注意的是,在渲染函数中,我们使用createElement方法来创建虚拟DOM节点。createElement接受三个参数:标签名、属性对象、子节点(或当前节点内容)。
除了直接返回虚拟DOM节点外,还可以使用JSX语法或者通过调用其他组件来构建更复杂的视图结构。具体写法会根据需求和个人喜好而有所不同。
3.2. Vue3 render函数创建一个按钮
创建MyButton.ts 【render函数中的h函数或createElement函数是用来创建虚拟DOM节点的】
import { emit } from "process"
import { defineComponent, h } from "vue"
export default defineComponent({
name: 'MyButton',
props:{
disabled:{
default:'false',
type:Boolean
}
},
render(context) {
return h('button', {
onclick: () => context.$emit('custom-click')
}, context.$slots)
}
})
像组件一样正常使用MyButton
<script setup lang="ts">
import MyButton from "./MyButton.ts"
const onClick = () => {
console.log('onClick')
}
</script>
<template>
<MyButton :disabled="false" @custom-click="onClick">
my button
</MyButton>
</template>
4. template模板的内容是如何渲染成真实的DOM的
在Vue中,template模板的内容是通过Vue的编译器将其转换为渲染函数,然后再由渲染函数生成虚拟DOM,并最终渲染到页面上。
具体的渲染过程如下:
-
编译阶段:Vue的编译器会将template模板编译成一个渲染函数。这个渲染函数接受一个createElement函数作为参数,用于创建虚拟DOM节点。
-
创建虚拟DOM:在组件实例化或更新时,Vue会调用渲染函数,并传入createElement函数。渲染函数根据模板定义的结构和内容,调用createElement来创建对应的虚拟DOM节点。
-
虚拟DOM更新:一旦创建了虚拟DOM节点,Vue会将其与之前的虚拟DOM进行比较,找出需要更新的部分。然后根据差异进行局部更新,而不是重新渲染整个组件。
-
真实DOM挂载:最后,经过虚拟DOM更新后得到最新的虚拟DOM树。Vue会将这个虚拟DOM树转换为真实的DOM元素,并挂载到页面上指定的位置。
总结起来,template模板通过编译器转换为渲染函数,在组件实例化或更新时调用该渲染函数生成虚拟DOM,并通过对比差异进行局部更新,最终将最新的虚拟DOM转换为真实的DOM元素并挂载到页面上。
转载自:https://juejin.cn/post/7252128430006206522