一文走近 Vue render 函数

举报
SHQ5785 发表于 2022/03/27 13:00:37 2022/03/27
【摘要】 一、前言Render 函数是 Vue2.x 新增的一个函数、主要用来提升节点渲染性能,它是基于 JavaScript 计算。使用 Render 函数将 Template 里面的节点解析成虚拟的Dom 。Vue 推荐在绝大多数情况下使用模板来创建 HTML。然而在一些场景中,你需要 JavaScript 的完全编程能力。这时你可以用渲染函数render,它比模板更接近编译器。在深入渲染函数之...

一、前言

Render 函数是 Vue2.x 新增的一个函数、主要用来提升节点渲染性能,它是基于 JavaScript 计算。使用 Render 函数将 Template 里面的节点解析成虚拟的Dom

Vue 推荐在绝大多数情况下使用模板来创建 HTML。然而在一些场景中,你需要 JavaScript 的完全编程能力。这时你可以用渲染函数render,它比模板更接近编译器。

在深入渲染函数之前,了解一些浏览器的工作原理是很重要的。以下面这段 HTML 为例:

<div>
  <h1>My title</h1>
  Some text content
  <!-- TODO: Add tagline -->
</div>

当浏览器解析这段代码时,它会通过建立一个DOM 节点树来保持追踪所有内容,如同你绘制一张家谱树来追踪家庭成员的发展一样。

上述 HTML 对应的 DOM 节点树如下图所示:
在这里插入图片描述
每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。

高效地更新所有这些节点会比较困难的,不过所幸你不必手动完成这个工作。你只需要告诉 Vue 希望页面上的 HTML 是什么,这可以是在一个模板里:

<h1>{{ blogTitle }}</h1>

或者一个渲染函数里:

render: function (createElement) {
  return createElement('h1', this.blogTitle)
}

在这两种情况下,Vue 都会自动保持页面更新,即便 blogTitle 发生了改变。

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

return createElement('h1', this.blogTitle)

createElement到底会返回什么呢?其实不是一个实际的DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

二、render函数

简单的说,在vue中我们使用模板HTML语法组建页面,使用render函数我们可以用js语言来构建DOM

因为vue虚拟DOM,所以在拿到template模板时也要转译成VNode函数,而用render函数构建DOM时,vue就免去了转译的过程。

当使用render函数描述虚拟DOM时,vue提供一个函数,这个函数是就构建虚拟DOM所需要的工具。官网起名createElement。还有约定的简写h,将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。

vm中有一个方法_c,也是这个函数的别名。

先看官网对 createElement的介绍:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签字符串,组件选项对象,或者解析上述任何一种的一个 async 异步函数,必要参数。
  'div',

  // {Object}
  // 一个包含模板相关属性的数据对象
  // 这样,您可以在 template 中使用这些属性。可选参数。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子节点 (VNodes),由 `createElement()` 构建而成,或使用字符串来生成“文本节点”。可选参数。组件树中的所有 VNode 必须是唯一的。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

就是说createElement(params1,params2,params3)接受三个参数,每个参数的类型官方介绍已经说明。

三、render函数应用

render:(h) => {
    return h('div',{ //给div绑定value属性
       props: {
           value:''
       }, 
       //给div绑定样式
       style:{
       	width:'30px'                                                                                   
       }, 
       //给div绑定点击事件  
       on: {
           click: () => {
               console.log('点击事件')
           }
       },
   })
}

四、深入数据对象

有一点要注意:正如 v-bind:classv-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 attribute
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    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 | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层 property
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

五、应用示例

以官网例子为例,这个组件可以根据父级组件给他传递的level来渲染h1还是h6,代码如下:

<template>  
<div>
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</div>
</template>
 
<script>
export defalut {
  // 接收父组件传来的 level
  props: {
    level: {
      type: Number,
      required: true
    }
  }
}
</script>

这种应用场景确实不多,但是也会遇到,比如现在有6种不同的状态,用1,2,3,4,5,6表示。我们要通过这些状态展示不同的图片,如果后台不给你返回图片地址只返回状态对应的数字(图片放在本地),用render函数在合适不过了(前提是状态有很多,2、3个就算了)。下面使用render函数重写上面的组件:

export default {
  render: function (createElement) {    // createElement可以写成h,这是公认写法
    // createElement 是 render 函数的参数,它本身也是个函数,并且有三个参数。
    
    /*
    这里说明它的三个参数:
    1、一个 HTML 标签字符串,组件选项对象,或者解析上述任何一种的一个 async 异步函数。类型:{String | Object | Function}。必需。
    2、一个包含模板相关属性的数据对象你可以在 template 中使用这些特性。类型:{Object}。可选。
    3、子虚拟节点 (VNodes),由 createElement() 构建而成,也可以使用字符串来生成“文本虚拟节点”。类型:{String | Array}。可选。
    */
    return createElement(
       // 参数1、标签名称,父组件传几,我这就是h几,不用再template上面加if,else了是不是很简洁实用?(必填)
      'h' + this.level, 
       // 参数2、这里相当于给标签加属性 例如:<div class='foo' style='color:red,font-size: 14px'></div>(可选)
      {
          // 与 `v-bind:class` 的 API 相同,
          // 接受一个字符串、对象或字符串和对象组成的数组
          'class': {
            foo: true,
            bar: false
          },
          // 与 `v-bind:style` 的 API 相同,
          // 接受一个字符串、对象,或对象组成的数组
          style: {
            color: 'red',
            fontSize: '14px'
          },
      },
      // 参数3、参数中渲染的标签子元素数组(可选)
      [
           'text',   // 文本节点直接写就可以
           _this.$slots.default,  // 所有不具名插槽,是个数组
           createElement('div', _header)   // createElement()创建的VNodes
      ]
    )
  },
  // 接收父组件传来的 level
  props: {
    level: {
      type: Number,
      required: true
    }
  }
}

六、总结

  1. render方法的实质就是生成template模板;
  2. 通过调用一个方法来生成,而这个方法是通过render方法的参数传递给它的;
  3. 这个方法有三个参数,分别提供标签名,标签相关属性,标签内部的html内容;
  4. 通过这三个参数,可以生成一个完整的模板。

备注

  • render方法可以使用JSX语法,但需要Babel plugin插件;
  • render方法里的第三个参数可以使用函数来生成多个组件(特别是如果他们相同的话),只要生成结果是一个数组,且数组元素都是VNode即可;

七、拓展阅读

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。