javascript渲染,js渲染器

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: