javascript渲染,js渲染器
这篇文章解释了渲染功能JSX。Vue建议在大多数情况下使用模板来创建你的HTML。但是,在某些场景下,你真的需要JavaScript完整的编程能力。这时候我们可以使用渲染函数,它比模板更接近编译器。有需要的朋友可以参考一下。
目录
I、基础II、节点、树和虚拟DOM1、虚拟DOM III、createElement参数1、深度数据对象2、完整示例3、约束IV、使用JavaScript代替模板函数1、v-if和v-for2、v-model3、事件键修饰符4、槽5、JSX VI、功能组件1、定向子元素
一、基础
Vue建议在绝大多数情况下使用模板来创建你的HTML。但是,在某些场景下,你真的需要JavaScript完整的编程能力。这时候我们可以使用渲染函数,它比模板更接近编译器。
我们来看一个简单的例子,其中的render函数非常实用。假设我们想要生成一些带有锚点的标题:
氕
a name= hello-world href= # hello-world rel= external no follow
你好世界!
/a
/h1
对于上面的 HTML,我们决定这样定义组件接口:
锚定航向:level=1Hello world!/锚定标题
当您开始编写一个只能通过level prop动态生成航向的组件时,您可能很快就会想到这个实现:
脚本类型=文本/x-模板 id=锚定标题-模板
h1 v-if=level===1
插槽/插槽
/h1
h2 v-else-if=level===2
插槽/插槽
/h2
h3 v-else-if=level===3
插槽/插槽
/h3
h4 v-else-if=level===4
插槽/插槽
/h4
h5 v-else-if=level===5
插槽/插槽
/h5
h6 v-else-if=level===6
插槽/插槽
/h6
/脚本
Vue.component(锚定标题,{
模板:“#锚定标题模板”,
道具:{
级别:{
类型:数量,
必填:真
}
}
})
模板在这里并不是最好的选择:不仅代码冗长,而且每一关的标题中都重复写了slot/slot,要插入锚元素的时候还得再重复一遍。
虽然模板在大多数组件中非常有用,但在这里显然不合适。因此,让我们尝试使用render函数重写上面的示例:
Vue.component(锚定标题,{
render:function(createElement){
返回createElement(
H this.level,//标签名称
这个。$slots.default //子节点数组
)
},
道具:{
级别:{
类型:数量,
必填:真
}
}
})
看起来简单多了!这大大简化了代码,但是您需要非常熟悉Vue的实例属性。在这个例子中,你需要知道当传递一个没有v-slot指令的子节点给一个组件时,比如anchored-heading中的Hello world!这些子节点存储在组件实例的slots.default中。如果你还不知道,建议在深入渲染函数之前先阅读实例属性API。
二、节点、树以及虚拟 DOM
在深入到渲染功能之前,了解一些浏览器的工作原理是很重要的。以下面的HTML为例:
差异
h1我的头衔/h1
一些文本内容
!- TODO:添加标语-
/div
当浏览器读取这些代码时,它将构建一个“DOM节点”树来跟踪一切,就像你会绘制一个家谱来跟踪家庭成员的发展一样。
上述 HTML 对应的 DOM 节点树如下图所示:
每个元素都是一个节点。每段文字也是一个节点。甚至注释也是节点。节点是页面的一部分。就像家谱一样,每个节点可以有子节点(也就是说,每个部分可以包含其他部分)。
高效地更新所有这些节点将会很困难,但幸运的是,您不必手动完成这项工作。你只需要告诉Vue你希望页面上的HTML是什么样的,可以是一个模板:
h1{{ blogTitle }}/h1
或者一个渲染函数里:
render:function(createElement){
返回createElement(h1 ,this.blogTitle)
}
在这两种情况下,Vue都会自动保持页面更新,即使blogTitle发生变化。
1、虚拟 DOM
Vue跟踪如何通过构建虚拟DOM来改变真实DOM。请仔细看这行代码:
返回createElement(h1 ,this.blogTitle)
createElement到底会返回什么?实际上,它不是一个真正的DOM元素。它更准确的名字可能是createNodeDescription,因为它包含的信息会告诉Vue页面需要渲染什么样的节点,包括它的子节点的描述信息。我们将这样的节点描述为“虚拟节点”,通常缩写为“VNode”。“虚拟DOM”是我们对Vue组件树构建的整个VNode树的称呼。
三、createElement 参数
接下来,你需要熟悉如何在createElement函数中使用模板中的那些函数。以下是createElement接受的参数:
//@返回{VNode}
创建元素(
//{字符串对象函数}
HTML标记名、组件选项对象或
//解析上述任何一个异步函数。必需的。
div ,
//{Object}
//模板中属性对应的数据对象。可选。
{
//(详见下一节)
},
//{String Array}
//由“createElement()”构建的子虚拟节点(VNodes),
//也可以用字符串生成一个“文本虚拟节点”。可选。
[
先写几个字,
CreateElement(h1 ,一个标题),
createElement(MyComponent,{
道具:{
some prop:“foobar”
}
})
]
)
1、深入数据对象
有一点需要注意:就像v-bind:class和v-bind:style在模板语法中被特殊对待一样,它们在VNode数据对象中也有相应的顶级字段。这个对象也允许你绑定普通的HTML属性,也允许你绑定DOM属性,比如innerHTML(它覆盖v-html指令)。
{
//与“v-bind: class”的API相同,
//接受字符串、对象或字符串和对象的数组
类别:{
没错,
酒吧:假
},
//与“v-bind: style”的API相同,
//接受字符串、对象或对象数组
风格:{
颜色:红色,
font size:“14px”
},
//普通HTML属性
属性:{
id:“foo”
},
//组件属性
道具:{
我的道具:“酒吧”
},
//DOM属性
domProps: {
innerHTML:“baz”
},
//事件侦听器在“on”内部,
//但是不再支持像‘v-on:keyup . enter’这样的装饰器。
//需要手动检查handler函数中的键码。
开:{
单击:this.clickHandler
},
//仅用于组件,侦听本机事件,不用于组件内部使用。
//` VM。$ emit `触发的事件。
nativeOn: {
单击:this.nativeClickHandler
},
//自定义指令。请注意,不能在binding中设置oldValue。
//赋值,因为Vue已经自动给你同步了。
指令:[
{
名称:我的自定义指令,
值:“2”,
表达式:“1 1”,
arg: foo ,
修饰符:{
酒吧:真的
}
}
],
//作用域槽的格式是
//{ name:props=VNode array VNode }
scopedSlots: {
默认值:props=createElement(span ,props.text)
},
//如果该组件是其他组件的子组件,则需要为该插槽指定一个名称。
插槽:插槽名称,
//其他特殊的顶级属性
密钥:“我的密钥”,
ref:“myRef”,
//如果在呈现函数中对多个元素应用相同的ref名称,
//那么“$ refs.myref”将成为一个数组。
refInFor:对
}
2、完整示例
有了这些知识,我们现在可以完成我们最开始想实现的组件:
var getChildrenTextContent=function(children){
return children.map(函数(节点){
返回节点.子节点
?getChildrenTextContent(node . children)
:节点.文本
}).联接(“”)
}
Vue.component(锚定标题,{
render:function(createElement){
//创建烤肉串样式ID
var heading id=getChildrenTextContent(this。$slots .默认值)。toLowerCase()。替换(/\W /g,-)。replace(/(^--$)/g,”)
返回createElement(
h 这个级别,
[
createElement(a ,{
属性:{
名称:headingId,
href:"# "标题id
}
},这个$老虎机。默认值)
]
)
},
道具:{
级别:{
类型:数量,
必填:真
}
}
})
3、约束
VNode 必须唯一
组件树中的所有虚拟节点必须是唯一的。这意味着,下面的渲染函数是不合法的:
render:function(createElement){
var myParagraphVNode=createElement( p , hi )
返回createElement(div ,[
//错误-重复的虚拟节点
我的段落,我的段落
])
}
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了20 个相同的段落:
render:function(createElement){
返回createElement(div ,
Array.apply(null,{ length: 20 }).地图(函数(){
返回createElement(p , hi )
})
)
}
四、使用 JavaScript 代替模板功能
1、v-if 和 v-for
只要在原生的Java脚本语言中可以轻松完成的操作,Vue的渲染函数就不会提供专有的替代方法。比如,在模板中使用的控制显示和五-代表:
ul v-if=items.length
Li v-for= item in item { item。name } }/Li
/ul
发现p . v .埃尔塞诺项目100元/人
这些都可以在渲染函数中用Java脚本语言的if/else和地图来重写:
道具:[物品],
render:function(createElement){
if (this.items.length) {
返回createElement(ul ,this.items.map(function (item) {
返回createElement(li ,item.name)
}))
}否则{
返回createElement(p ,未找到任何项目)
}
}
2、v-model
渲染函数中没有与v型车的直接对应——你必须自己实现相应的逻辑:
道具:[值],
render:function(createElement){
var self=this
返回createElement(input ,{
domProps: {
价值:自我价值
},
开:{
输入:函数(事件){
自我emit(input ,event.target.value)
}
}
})
}
这就是深入底层的代价,但与v型车相比,这可以让你更好地控制交互细节。
3、事件 按键修饰符
对于。被动。捕获和。一次这些事件修饰符,Vue提供了相应的前缀可以用于开启:
修饰符
前缀。消极的。捕获
!一次
~。捕捉。一次或。一次。捕获
~!
例如:
开:{
!点击:这个。dothisincapturemode,
~keyup: this.doThisOnce,
~!鼠标悬停:这个。dothionceincapturingmode
}
对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:
修饰符
处理函数中的等价操作。停止
event.stopPropagation()。预防
事件。预防默认()。自己
if (event.target!==event.currentTarget)返回
按键:输入。13
if (event.keyCode!==13) return(对于别的按键修饰符来说,可将13改为另一个按键码)
修饰键:ctrl,0 .alt,0 .移位,自指的
如果(!event.ctrlKey)返回(将ctrlKey分别修改为中高音键、shiftKey键键或者元键)
这里是一个使用所有修饰符的例子:
开:{
键盘输入:函数(事件){
//如果触发事件的元素不是事件绑定的元素
//则返回
if (event.target!==event.currentTarget)返回
//如果按下去的不是进入键或者
//没有同时按下变化键
//则返回
如果(!event.shiftKey event.keyCode!==13)返回
//阻止事件冒泡
event.stopPropagation()
//阻止该元素默认的击键事件
事件。预防默认()
//.
}
}
4、插槽
你可以通过这个。$老虎机访问静态插槽的内容,每个插槽都是一个虚拟节点数组:
render:function(createElement){
//`divslot/slot/div
返回createElement(div ,this .$老虎机。默认值)
}
也可以通过这个10.25美元scopedSlots访问作用域插槽,每个作用域插槽都是一个返回若干虚拟节点的函数:
道具:[消息],
render:function(createElement){
//` div slot:text= message /slot/div ` 1
返回createElement(div ,[
这个。$scopedSlots.default({
文本:this.message
})
])
}
如果希望将作用域槽传递给具有呈现功能的子组件,可以利用VNode数据对象中的scopedSlots字段:
render:function(createElement){
//` div child v-slot= props span { { props . text } }/span/child/div ` 1
返回createElement(div ,[
createElement(child ,{
//在数据对象中传递scopedSlots
//格式为{name: props=VNode ArrayVNode}
scopedSlots: {
默认:功能(道具){
返回createElement(span ,props.text)
}
}
})
])
}
五、JSX
如果你写了很多渲染函数,你可能会发现下面的代码写起来很痛苦:
创建元素(
锚定标题,{
道具:{
级别:1
}
}, [
createElement(span , Hello )。
世界!
]
)
特别是对应的模板如此简单的情况下:
锚定标题:级别=1
spanHello/span world!
/锚定标题
这就是为什么在Vue中有一个使用JSX语法的巴别塔插件,它可以让我们回到更接近模板的语法。
从导入锚定标题。/AnchoredHeading.vue
新Vue({
埃尔: #演示,
渲染:函数(h) {
返回(
AnchoredHeading级别={1}
spanHello/span world!
/AnchoredHeading
)
}
})
以H作为createElement的别名是Vue生态系统中常见的做法,实际上是JSX所要求的。从Vue的Babel插件3.4.0版本开始,我们会自动注入const h=this。$createElement到任何方法和getter中,用ES2015语法声明JSX(不在函数或箭头函数中),这样可以去掉(h)参数。对于早期版本的插件,如果H在当前范围内不可用,应用程序将抛出一个错误。
六、函数式组件
之前创建的锚头组件比较简单,没有管理任何状态,没有监控任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受某种道具的函数。在这种情况下,我们可以将组件标记为功能性的,这意味着它没有状态(没有响应数据)和实例(没有上下文)。功能组件看起来像这样:
Vue.component(我的组件,{
功能性:真的,
//Props是可选的。
道具:{
//.
},
//以弥补缺失的实例
//提供第二个参数作为上下文
render: function (createElement,context) {
//.
}
})
注意:在2.3.0之前的版本中,如果一个功能组件想要接收prop,props选项是必需的。在2.3.0或更高版本中,可以省略props选项,所有组件上的属性将自动隐式解析为prop。
当使用功能组件时,引用将是HTMLElement,因为它们是无状态的和实例化的。
在2.5.0和更高版本中,如果使用单文件组件,那么基于模板的功能组件可以这样声明:
模板功能
/模板
组件所需的一切都通过context参数传递,该参数是一个包含以下字段的对象:
道具:提供所有道具的物品。
children:vnode子节点的数组。
Slots:返回包含所有插槽的对象的函数。
Scoped slots: (2.6.0)公开传入Scoped slots的对象:(2.6.0)公共插槽也作为函数公开。
Data:传递给组件的整个数据对象,作为createElement的第二个参数传递给组件。
父组件:对父组件的引用。
Listeners:(2.3.0)包含由当前组件的父组件注册的所有事件侦听器的对象。这是data.on的别名
注入:(2.3.0)如果使用了注入选项,则对象包含应该注入的属性。
在添加functional: true之后,我们需要更新我们的锚标题组件的呈现函数,向它添加上下文参数,并更新这个。$slots.default到context.children,然后将this.level更新到context.props.level
因为功能组件只是功能,渲染开销要低很多。
它们作为包装组件也非常有用。比如,当你需要做这些时:
以编程方式选择多个组件中的一个来呈现;
在将子对象、道具和数据传递给子组件之前,对它们进行操作。
下面是一个智能列表组件的示例,它可以根据传入属性的值呈现更具体的组件:
var EmptyList={ /*.*/}
var TableList={ /*.*/}
var OrderedList={ /*.*/}
var UnorderedList={ /*.*/}
Vue.component(智能列表,{
功能性:真的,
道具:{
项目:{
类型:数组,
必填:真
},
isOrdered:布尔型
},
render: function (createElement,context) {
函数appropriated listcomponent(){
var items=context.props.items
if (items.length===0)返回EmptyList
if (typeof items[0]===object )返回表列表
if (context.props.isOrdered)返回OrderedList
返回无序列表
}
返回createElement(
appropriateListComponent(),
上下文.数据,
背景.儿童
)
}
})
1、向子元素或子组件传递 attribute 和事件
在一个通用组件中,未定义为prop的属性会自动添加到组件的根元素中,现有的同名属性会被替换或与之智能合并。
然而函数式组件要求你显式定义该行为:
Vue.component(我的功能按钮,{
功能性:真的,
render: function (createElement,context) {
//对任何属性、事件监听器、子节点等完全透明。
返回createElement(button ,context.data,context.children)
}
})
通过将context.data作为第二个参数传递给createElement,我们传递了my-functional-button之上的所有属性和事件侦听器。事实上,这是如此的透明,以至于那些事件甚至不需要。原生修饰符。
如果使用基于模板的功能组件,还需要手动添加属性和侦听器。因为我们可以访问其独立的上下文内容,所以我们可以使用data.attrs来传递任何HTML属性,或者使用listeners(即data.on的别名)来传递任何事件侦听器。
模板功能
按钮
btn btn-primary
v-bind=data.attrs
v-on=listeners
插槽/
/按钮
/模板
2、slots() 和 children 对比
你可能想知道为什么同时需要slots()和children。不是插槽()。默认类似儿童?在某些场景下是——,但是如果是下面这个有子节点的功能组件呢?
我的功能组件
p v型槽:foo
第一
/p
p秒/p
/我的功能组件
对于这个组件,孩子会给你两个段落标签,而slots()。默认只会传递第二个匿名段落标签,和slots()。foo将传递第一个命名的段落标签。同时拥有children和slots(),那么可以选择让组件感知一个slot机制,或者简单的传递children,交给其他组件处理。
七、模板编译
你可能有兴趣知道Vue的模板实际上被编译成了一个渲染函数。这是一个实现细节,通常不需要关心。但是如果你想看看模板的功能是怎么编译的,你可能会觉得很有趣。下面是一个使用Vue.compile实时编译模板字符串的简单示例:
差异
页眉
h1我是模板!/h1
/页眉
p v-if= message“{ message } }/p
p v-elseNo消息。/p
/div
render:
匿名函数(
) {
with(this){return _c(div ,[_m(0),(message)?_c(p ,[_v(_s(消息))]):_c(p ,[_v(无消息。)])])}
}
staticRenderFns:
_m(0):函数匿名(
) {
with(this){return _c(header ,[_c(h1 ,[_v(我是模板!)])])}
}
这就是这篇关于渲染函数JSX的细节。关于渲染函数JSX的更多细节,请搜索我们以前的文章或继续浏览下面的相关文章。希望你以后能支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。