# Vue 中的 render 函数和 jsx

# 从官方文档的例子说起

需要这样一个组件

<anchored-heading :level="1">Hello world!</anchored-heading>

template 写法:

<template>
  <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>
</template>

render 函数写法:

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // 标签名称
      this.$slots.default // 子节点数组
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

好处:代码精简,不需要写重复代码

其实,template 也可以:

<component :is="'h' + level"><slot></slot></component>

# 关于 Vue 中模板的写法

Vue 中模板的写法总共有几种?

  • template:
    • '#' + 选择器,使用匹配元素的 innerHTML 作为模板:"#my-component"
    • 字符串:"<li>{{ todo.text }}</li>"
    • 单文件组件中的 template 标签,最终会通过 vue-loader 提取为字符串,然后通过 vue-template-compiler 编译成 render 函数,所以可以使用 Pug 写法
  • render:
    • createElement 函数
    • jsx

渲染过程:template -> AST(抽象语法树)-> render 函数 -> VNode(虚拟 dom) -> 真实 dom

render 函数的性能更好,优先级更高

# 运行时 + 编译器 vs. 只包含运行时

// 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 不需要编译器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

对应 webpack 配置:

module.exports = {
  // ...
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}

当使用 vue-loader 的时候,*.vue 文件内部的模板会在构建时预编译成 JavaScript

使用运行时版本性能更好(不需要编译 template),体积更小(30%)

Vue2 dist 目录下各个文件的区别 - M.M.F 小屋 (opens new window)

# createElement 参数和数据对象

请看官方文档:渲染函数 & JSX — Vue.js (opens new window)

# 使用 JavaScript 代替模板功能

  • v-if: if & else,或三元表达式
  • v-for: Array.prototype.map
  • v-model:
const data = {
  props: {
    value: this.value
  },
  on: {
    input: (val) => {
      this.value = val
    }
  }
}

# JSX

更接近模板语法,比使用 createElement 函数简洁

  • 用括号包裹 jsx 代码
  • 需要写 js 逻辑的地方用 { ... },包括动态属性
  • 事件监听用 on-eventName
  • 可直接将 jsx 片段赋值给变量

需要插件进行编译,所以不能在 HTML 中直接写 jsx

插件:vuejs/jsx: monorepo for Babel / Vue JSX related packages (opens new window)

# 模板组件中使用渲染函数

当你使用的组件是用 template 的方式编写,但是又想从外部传一个渲染函数进来渲染某一块内容时,可以利用一个更小的组件来做这个事情,如:

<template>
  <div class="my-component">
    <div v-for="item in arr" :key="item.id">
      <custom-header :item="item"></custom-header>
      <div class="content">...</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'my-component',
  props: {
    renderHeader: Function, // 从父组件传过来的渲染函数
    arr: Array
  },
  components: {
    CustomHeader: {
      props: {
        item: Object
      },
      render(h) {
        // 通过渲染函数渲染自定义内容
        return this.$parent.renderHeader(h, this.item)
      }
    }
  }
}
</script>

感兴趣的话可以看看 element ui 中这个更复杂的例子 (opens new window)

# 函数式组件

无状态,无生命周期,无实例

函数式组件只是函数,所以渲染开销也低很多

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

模板语法:

<template functional>
</template>

# 总结

# render 函数

# 优点

  • 将js发挥到极致,逻辑性比较强,适合复杂的组件封装
  • 性能好
  • 灵活,比如可直接传一个函数到子组件中渲染内容

# 缺点

  • 可读性不高
  • 代码块可能会比较分散

# template

# 优点

  • 可读性高,一目了然,代码好管理
  • 有更多的语法糖,如指令、事件修饰符等

# 缺点

  • 性能不够好(非单文件组件中)
  • 编写复杂组件时代码,模板较大,不好管理

# 性能

函数式组件 > render 函数 ≈ 单文件组件 template > 字符串 template > 选择器指定的 template

# 参考资料

更新时间: 2021年12月31日星期五下午3点12分