佛山网站推广优化公司,百度百度一下你就知道主页,google网站登陆模板,濮阳高端网站建设目录
h函数
方法1. 在Options API中的使用
方法2. 在Composition API中的使用
Vue 2中的渲染函数
基础
vue2
vue3
vue3--声明渲染函数
节点、树以及虚拟 DOM
虚拟 DOM
createElement 参数
深入数据对象
约束
vue2
vue3
使用 JavaScript 代替模板功能…目录
h函数
方法1. 在Options API中的使用
方法2. 在Composition API中的使用
Vue 2中的渲染函数
基础
vue2
vue3
vue3--声明渲染函数
节点、树以及虚拟 DOM
虚拟 DOM
createElement 参数
深入数据对象
约束
vue2
vue3
使用 JavaScript 代替模板功能
v-if 和 v-for vue2
vue 3
v-model
vue2
vue3 事件 按键修饰符
vue2
vue3 插槽
vue2
vue3
总结
渲染函数参数
2.x 语法
3.x 语法
VNode Prop 格式化
2.x 语法
3.x 语法
注册组件
2.x 语法
3.x 语法 当谈到Vue.js的渐进式框架时很快会想到它的核心功能之一渲染函数。渲染函数是Vue.js的一种强大工具允许您以编程方式创建和控制虚拟DOM。在本文中我们将探讨Vue 2和Vue 3中的渲染函数的不同之处以及如何使用它们来构建灵活的用户界面。
h函数
h函数可以在两个地方使用
Options API 的render函数选项中setup函数选项中setup本身需要是一个函数类型函数再返回h函数创建的VNode
h函数参数 第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop第三个参数是子节点。 当创建一个组件的 vnode 时子节点必须以插槽函数进行传递。如果组件只有默认槽可以使用单个插槽函数进行传递。否则必须以插槽函数的对象形式来传递。 为了方便阅读当子节点不是插槽对象时可以省略 prop 参数。 比有如下一个template结构, 需要使用h函数创建出来
方法1. 在Options API中的使用
import { h } from vueexport default {render() {return h(div, {className: app}, [h(h2, {className: title}, 我是标题),h(p, null, 我是内容),])}
} render函数不仅可以传入普通元素, 也可以传入一个组件 import { h } from vue
import Home from ./home.vueexport default {render() {return h(div, { className: app }, [// 因为不是在模板中使用, 因此无需注册, 直接使用h(Home)])}
}
方法2. 在Composition API中的使用
script
import { h } from vueexport default {setup() {// setup是一个函数, 让这个函数再返回一个函数return () h(div, { class: app }, [h(h2, { class: title }, 我是标题),h(p, null, 我是内容)])}
}
/script如果是在script setup标签中使用h函数, 需要如下方式(会变得很繁琐) template!-- 将render函数变量写在temolate标签中 --render/render
/templatescript setup
import { h, ref } from vueconst conter ref(0)const increment () {conter.value }const decrement () {conter.value --}// 将这个render函数保存到一个变量中const render () h(div, { class: app }, [h(h2, { class: title }, 当前计数: ${conter.value}),h(button, { onclick: increment }, ),h(button, { onclick: decrement }, -)])
/script Vue 2中的渲染函数
在Vue 2中渲染函数是一个重要的概念但相对较复杂。它通常使用JavaScript的createElement函数来创建虚拟DOM节点。下面是一个简单的Vue 2渲染函数示例
Vue.component(my-component, {render: function (createElement) {return createElement(div, Hello, Vue 2!)}
})在这个示例中我们创建了一个Vue组件其中的render函数接受一个createElement参数用于创建一个包含文本内容的div元素。
虽然Vue 2的渲染函数非常强大但它的语法可能会相对复杂并且不够直观。这使得在开发大型应用程序时理解和维护渲染函数可能会变得困难。
基础 Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数它比模板更接近编译器。
让我们深入一个简单的例子这个例子里 render 函数很实用。假设我们要生成一些带锚点的标题 vue2
h1a namehello-world href#hello-worldHello world!/a
/h1
对于2上面的 HTML你决定这样定义组件接口
anchored-heading :level1Hello world!/anchored-heading
当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时你可能很快想到这样实现
script typetext/x-template idanchored-heading-templateh1 v-iflevel 1slot/slot/h1h2 v-else-iflevel 2slot/slot/h2h3 v-else-iflevel 3slot/slot/h3h4 v-else-iflevel 4slot/slot/h4h5 v-else-iflevel 5slot/slot/h5h6 v-else-iflevel 6slot/slot/h6
/script
Vue.component(anchored-heading, {template: #anchored-heading-template,props: {level: {type: Number,required: true}}
})
这里用模板并不是最好的选择不但代码冗长而且在每一个级别的标题中重复书写了 slot/slot在要插入锚点元素时还要再次重复。
虽然模板在大多数组件中都非常好用但是显然在这里它就不合适了。那么我们来尝试使用 render 函数重写上面的例子
Vue.component(anchored-heading, {render: function (createElement) {return createElement(h this.level, // 标签名称this.$slots.default // 子节点数组)},props: {level: {type: Number,required: true}}
})
看起来简单多了这样代码精简很多但是需要非常熟悉 Vue 的实例 property。在这个例子中你需要知道向组件中传递不带 v-slot 指令的子节点时比如 anchored-heading 中的 Hello world!这些子节点被存储在组件实例中的 $slots.default 中。如果你还不了解在深入渲染函数之前推荐阅读实例 property API。
vue3
Vue 提供了一个 h() 函数用于创建 vnodes
import { h } from vueconst vnode h(div, // type{ id: foo, class: bar }, // props[/* children */]
)
h() 是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode()但当你需要多次使用渲染函数时一个简短的名字会更省力。
h() 函数的使用方式非常的灵活 // 除了类型必填以外其他的参数都是可选的
h(div)
h(div, { id: foo })// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h(div, { class: bar, innerHTML: hello })// 像 .prop 和 .attr 这样的的属性修饰符
// 可以分别通过 . 和 ^ 前缀来添加
h(div, { .name: some-name, ^width: 100 })// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h(div, { class: [foo, { bar }], style: { color: red } })// 事件监听器应以 onXxx 的形式书写
h(div, { onClick: () {} })// children 可以是一个字符串
h(div, { id: foo }, hello)// 没有 props 时可以省略不写
h(div, hello)
h(div, [h(span, hello)])// children 数组可以同时包含 vnodes 与字符串
h(div, [hello, h(span, hello)])
得到的 vnode 为如下形式
const vnode h(div, { id: foo }, [])vnode.type // div
vnode.props // { id: foo }
vnode.children // []
vnode.key // null 注意事项 完整的 VNode 接口包含其他内部属性但是强烈建议避免使用这些没有在这里列举出的属性。这样能够避免因内部属性变更而导致的不兼容性问题。 vue3--声明渲染函数
我们可以使用 render 选项来声明渲染函数
import { h } from vueexport default {data() {return {msg: hello}},render() {return h(div, this.msg)}
}
render() 函数可以访问同一个 this 组件实例。
除了返回一个单独的 vnode 之外你还可以返回字符串或是数组
export default {render() {return hello world!}
} import { h } from vueexport default {render() {// 用数组来返回多个根节点return [h(div),h(div),h(div)]}
}
如果一个渲染函数组件不需要任何实例状态为了简洁起见它们也可以直接被声明为一个函数
function Hello() {return hello world!
}
没错这就是一个合法的 Vue 组件参阅函数式组件来了解更多语法细节。
节点、树以及虚拟 DOM
在深入渲染函数之前了解一些浏览器的工作原理是很重要的。以下面这段 HTML 为例
divh1My title/h1Some text content!-- TODO: Add tagline --
/div
当浏览器读到这些代码时它会建立一个“DOM 节点”树来保持追踪所有内容如同你会画一张家谱树来追踪家庭成员的发展一样。 上述 HTML 对应的 DOM 节点树如下图所示
每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。
高效地更新所有这些节点会是比较困难的不过所幸你不必手动完成这个工作。你只需要告诉 Vue 你希望页面上的 HTML 是什么这可以是在一个模板里
h1{{ blogTitle }}/h1 或者一个渲染函数里
render: function (createElement) {return createElement(h1, this.blogTitle)
}
在这两种情况下Vue 都会自动保持页面的更新即便 blogTitle 发生了改变。
虚拟 DOM
Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码
return createElement(h1, this.blogTitle)
createElement 到底会返回什么呢其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
createElement 参数
接下来你需要熟悉的是如何在 createElement 函数中使用模板中的那些功能。这里是 createElement 接受的参数
// returns {VNode}
createElement(// {String | Object | Function}// 一个 HTML 标签名、组件选项对象或者// resolve 了上述任何一种的一个 async 函数。必填项。div,// {Object}// 一个与模板中 attribute 对应的数据对象。可选。{// (详情见下一节)},// {String | Array}// 子级虚拟节点 (VNodes)由 createElement() 构建而成// 也可以使用字符串来生成“文本虚拟节点”。可选。[先写一些文字,createElement(h1, 一则头条),createElement(MyComponent, {props: {someProp: foobar}})]
)
深入数据对象
有一点要注意正如 v-bind:class 和 v-bind:style 在模板语法中会被特别对待一样它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML attribute也允许绑定如 innerHTML 这样的 DOM property (这会覆盖 v-html 指令)。
{// 与 v-bind:class 的 API 相同// 接受一个字符串、对象或字符串和对象组成的数组class: {foo: true,bar: false},// 与 v-bind:style 的 API 相同// 接受一个字符串、对象或对象组成的数组style: {color: red,fontSize: 14px},// 普通的 HTML attributeattrs: {id: foo},// 组件 propprops: {myProp: bar},// DOM propertydomProps: {innerHTML: baz},// 事件监听器在 on 内// 但不再支持如 v-on:keyup.enter 这样的修饰器。// 需要在处理函数中手动检查 keyCode。on: {click: this.clickHandler},// 仅用于组件用于监听原生事件而不是组件内部使用// vm.$emit 触发的事件。nativeOn: {click: this.nativeClickHandler},// 自定义指令。注意你无法对 binding 中的 oldValue// 赋值因为 Vue 已经自动为你进行了同步。directives: [{name: my-custom-directive,value: 2,expression: 1 1,arg: foo,modifiers: {bar: true}}],// 作用域插槽的格式为// { name: props VNode | ArrayVNode }scopedSlots: {default: props createElement(span, props.text)},// 如果组件是其它组件的子组件需为插槽指定名称slot: name-of-slot,// 其它特殊顶层 propertykey: myKey,ref: myRef,// 如果你在渲染函数中给多个元素都应用了相同的 ref 名// 那么 $refs.myRef 会变成一个数组。refInFor: true
}
约束 VNode 必须唯一 组件树中的所有 VNode 必须是唯一的。这意味着下面的渲染函数是不合法的 vue2
render: function (createElement) {var myParagraphVNode createElement(p, hi)return createElement(div, [// 错误 - 重复的 VNodemyParagraphVNode, myParagraphVNode])
}
如果你真的需要重复很多次的元素/组件你可以使用工厂函数来实现。例如下面这渲染函数用完全合法的方式渲染了 20 个相同的段落
render: function (createElement) {return createElement(div,Array.apply(null, { length: 20 }).map(function () {return createElement(p, hi)}))
}
vue3
组件树中的 vnodes 必须是唯一的。下面是错误示范
function render() {const p h(p, hi)return h(div, [// 啊哦重复的 vnodes 是无效的p,p])
}
如果你真的非常想在页面上渲染多个重复的元素或者组件你可以使用一个工厂函数来做这件事。比如下面的这个渲染函数就可以完美渲染出 20 个相同的段落
function render() {return h(div,Array.from({ length: 20 }).map(() {return h(p, hi)}))
} 使用 JavaScript 代替模板功能
v-if 和 v-for vue2
只要在原生的 JavaScript 中可以轻松完成的操作Vue 的渲染函数就不会提供专有的替代方法。比如在模板中使用的 v-if 和 v-for
ul v-ifitems.lengthli v-foritem in items{{ item.name }}/li
/ul
p v-elseNo items found./p
这些都可以在渲染函数中用 JavaScript 的 if/else 和 map 来重写
props: [items],
render: function (createElement) {if (this.items.length) {return createElement(ul, this.items.map(function (item) {return createElement(li, item.name)}))} else {return createElement(p, No items found.)}
}
vue 3
v-if
divdiv v-ifokyes/divspan v-elseno/span
/div
等价于使用如下渲染函数
h(div, [this.ok ? h(div, yes) : h(span, no)])
v-for
ulli v-for{ id, text } in items :keyid{{ text }}/li
/ul
等价于使用如下渲染函数
h(ul,this.items.map(({ id, text }) {return h(li, { key: id }, text)})
) v-model
vue2
渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑
props: [value],
render: function (createElement) {var self thisreturn createElement(input, {domProps: {value: self.value},on: {input: function (event) {self.$emit(input, event.target.value)}}})
}
这就是深入底层的代价但与 v-model 相比这可以让你更好地控制交互细节。
vue3
v-model 指令扩展为 modelValue 和 onUpdate:modelValue 在模板编译过程中我们必须自己提供这些 props
export default {props: [modelValue],emits: [update:modelValue],render() {return h(SomeComponent, {modelValue: this.modelValue,onUpdate:modelValue: (value) this.$emit(update:modelValue, value)})}
} 事件 按键修饰符
vue2
对于 .passive、.capture 和 .once 这些事件修饰符Vue 提供了相应的前缀可以用于 on on: {!click: this.doThisInCapturingMode,~keyup: this.doThisOnce,~!mouseover: this.doThisOnceInCapturingMode
}
对于所有其它的修饰符私有前缀都不是必须的因为你可以在事件处理函数中使用事件方法 这里是一个使用所有修饰符的例子
on: {keyup: function (event) {// 如果触发事件的元素不是事件绑定的元素// 则返回if (event.target ! event.currentTarget) return// 如果按下去的不是 enter 键或者// 没有同时按下 shift 键// 则返回if (!event.shiftKey || event.keyCode ! 13) return// 阻止 事件冒泡event.stopPropagation()// 阻止该元素默认的 keyup 事件event.preventDefault()// ...}
}
vue3
对于 .passive、.capture 和 .once 事件修饰符可以使用驼峰写法将他们拼接在事件名后面
实例
h(input, {onClickCapture() {/* 捕捉模式中的监听器 */},onKeyupOnce() {/* 只触发一次 */},onMouseoverOnceCapture() {/* 单次 捕捉 */}
})
inputonClickCapture{() {}}onKeyupOnce{() {}}onMouseoverOnceCapture{() {}}
/ 对于事件和按键修饰符可以使用 withModifiers 函数
import { withModifiers } from vueh(div, {onClick: withModifiers(() {}, [self])
})
div onClick{withModifiers(() {}, [self])} / 插槽
vue2
你可以通过 this.$slots 访问静态插槽的内容每个插槽都是一个 VNode 数组
render: function (createElement) {// divslot/slot/divreturn createElement(div, this.$slots.default)
}
也可以通过 this.$scopedSlots 访问作用域插槽每个作用域插槽都是一个返回若干 VNode 的函数
props: [message],
render: function (createElement) {// divslot :textmessage/slot/divreturn createElement(div, [this.$scopedSlots.default({text: this.message})])
}
如果要用渲染函数向子组件中传递作用域插槽可以利用 VNode 数据对象中的 scopedSlots 字段
render: function (createElement) {// divchild v-slotpropsspan{{ props.text }}/span/child/divreturn createElement(div, [createElement(child, {// 在数据对象中传递 scopedSlots// 格式为 { name: props VNode | ArrayVNode }scopedSlots: {default: function (props) {return createElement(span, props.text)}}})])
}
vue3
在渲染函数中可以通过 this.$slots 来访问插槽
export default {props: [message],render() {return [// divslot //divh(div, this.$slots.default()),// divslot namefooter :textmessage //divh(div,this.$slots.footer({text: this.message}))]}
}
传递插槽
向组件传递子元素的方式与向元素传递子元素的方式有些许不同。我们需要传递一个插槽函数或者是一个包含插槽函数的对象而非是数组插槽函数的返回值同一个正常的渲染函数的返回值一样——并且在子组件中被访问时总是会被转化为一个 vnodes 数组。
h(MyComponent, () hello)// 具名插槽
// 注意 null 是必需的
// 以避免 slot 对象被当成 prop 处理
h(MyComponent, null, {default: () default slot,foo: () h(div, foo),bar: () [h(span, one), h(span, two)]
})
插槽以函数的形式传递使得它们可以被子组件懒调用。这能确保它被注册为子组件的依赖关系而不是父组件。这使得更新更加准确及有效。
总结
此更改不会影响 template 用户。
以下是更改的简要总结
h 现在是全局导入而不是作为参数传递给渲染函数更改渲染函数参数使其在有状态组件和函数组件的表现更加一致VNode 现在有一个扁平的 prop 结构
请继续阅读来获取更多信息
渲染函数参数
2.x 语法
在 2.x 中render 函数会自动接收 h 函数 (它是 createElement 的惯用别名) 作为参数
// Vue 2 渲染函数示例
export default {render(h) {return h(div)}
}3.x 语法
在 3.x 中h 函数现在是全局导入的而不是作为参数自动传递。
// Vue 3 渲染函数示例
import { h } from vueexport default {render() {return h(div)}
}VNode Prop 格式化
2.x 语法
在 2.x 中domProps 包含 VNode prop 中的嵌套列表
// 2.x
{staticClass: button,class: { is-outlined: isOutlined },staticStyle: { color: #34495E },style: { backgroundColor: buttonColor },attrs: { id: submit },domProps: { innerHTML: },on: { click: submitForm },key: submit-button
}3.x 语法
在 3.x 中整个 VNode prop 的结构都是扁平的。使用上面的例子来看看它现在的样子。
// 3.x 语法
{class: [button, { is-outlined: isOutlined }],style: [{ color: #34495E }, { backgroundColor: buttonColor }],id: submit,innerHTML: ,onClick: submitForm,key: submit-button
}注册组件
2.x 语法
在 2.x 中注册一个组件后把组件名作为字符串传递给渲染函数的第一个参数它可以正常地工作
// 2.x
Vue.component(button-counter, {data() {return {count: 0}},template: button clickcountClicked {{ count }} times./button
})export default {render(h) {return h(button-counter)}
}3.x 语法
在 3.x 中由于 VNode 是上下文无关的不能再用字符串 ID 隐式查找已注册组件。取而代之的是需要使用一个导入的 resolveComponent 方法
// 3.x
import { h, resolveComponent } from vueexport default {setup() {const ButtonCounter resolveComponent(button-counter)return () h(ButtonCounter)}
}