微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

如何构建无渲染Vue组件?

2018-08-20 18:51 无明 译
作者|Samuel Oloruntoba
译者|无明

很多人把 Vue 比作 React 和 Angular 相结合的产物,我也一直很有同感。Vue 的学习曲线这么平缓,难怪有那么多人喜欢它。

无渲染组件是指不渲染任何内容的组件。在这篇文章中,我们将介绍 Vue 如何处理组件的渲染,我们还将看到如何使用 render() 函数创建无渲染组件。

解开 Vue 渲染组件的面纱

Vue 提供了很多种方法用于定义组件的标记:

  • 单文件组件让我们可以像普通的 HTML 文件一样定义组件及其标记。

  • template 属性让我们可以使用 JavaScript 的模板字面量来定义组件的标记。

  • el 属性让 Vue 通过查询 DOM 来获取用作模板的标记。

你可能听过这样的一句话:Vue 及其所有组件都只不过是 JavaScript。如果你认为这句话是错误的,我可以理解,毕竟我们还是需要写很多 HTML 和 CSS 的代码。下面我们以单文件组件为例。

在使用单文件组件时,我们可以这样定义一个 Vue 组件:

<template>
  <div class="mood">
    {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
  </div>
</template>

<script>
  export default {
    data: () => ({ todayIsSunny: true })
  }
</script>

<style>
  .mood:after {
    content: '&#x1f389;&#x1f389;';
  }
</style>

我们怎么能说 Vue 只是“JavaScript”呢?但归根结底,它确实是的。Vue 试图简化样式和其他资源的管理,只是 Vue 并没有直接做这些事情,而是把这些工作留给了构建过程,比如 webpack。

webpack 在处理.vue 文件时,会对其执行一个转换。在转换过程中,CSS 被从组件中提取出来,并放到单独的文件中,剩余部分则被转换为 JavaScript。例如:

export default {
  template: `
    <div class="mood">
      {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
    </div>`,
  data: () => ({ todayIsSunny: true })
}

这个与之前的不太一样。要想知道接下来会发生什么,我们需要先了解一下模板编译器。

模板编译器和渲染函数

当模板编译器遇到这段代码:

{
  template: `<div class="mood">...</div>`,
  data: () => ({ todayIsSunny: true })
}

它将 template 属性提取出来,并将它的内容编译为 JavaScript,然后将一个渲染函数添加到组件对象中。这个渲染函数将返回已转换为 JavaScript 的 template 属性的内容。

这是渲染函数的示例,对应上面的 template:

...
render(h) {
  return h(
    'div',
    { class: 'mood' },
    this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me'
  )
}
...

有关渲染函数的更多信息,请参阅官方文档 https://vuejs.org/v2/guide/render-function.html。

现在,当组件对象被传给 Vue 时,组件的渲染函数会经过一些优化并变成 VNode(虚拟节点),然后 VNode 被传给 snabbdom(Vue 内部用于管理虚拟 DOM 的库)。

Vue 通过 VNode 的方式来渲染组件。顺便说一下,渲染函数还允许我们在 Vue 中使用 JSX!

我们不需要等待 Vue 为我们添加渲染函数——我们可以自定义渲染函数,而且它应该优先于 el 或 template 属性。

如果你使用 Vue CLI 或某些自定义构建过程来构建 Vue 组件,就不需要导入模板编译器,因为这样可能会增加文件的体积。同时,你的组件会被预先优化,带来出色的性能和轻量级的 JavaScript 文件。

无渲染 Vue 组件

无渲染组件是指不渲染任何内容的组件。那么为什么我们需要不渲染任何内容的组件?

我们可以这样理解无渲染组件:为一个组件创建通用功能抽象,然后通过扩展这个组件来创建更好更健壮的组件,或者说,遵循 S.O.L.I.D 原则。

根据 S.O.L.I.D 的单一责任原则:

一个类应该只有一个用途。

我们将这个概念移植到 Vue 开发中,让每个组件只提供一个用途。

你可能有一个叫作“密码输入”的组件,它会渲染密码输入框。这种方法的问题在于,当你想在另一个项目中重用这个组件时,必须通过修改组件的源代码来让它的样式与新项目的样式保持一致。

这打破了 S.O.L.I.D 的开放封闭原则,这个原则规定:

类或组件应该为扩展而开放,为修改而封闭。

也就是说,你应该扩展它,而不是直接修改组件的源代码。

Vue 遵循了 S.O.L.I.D. 原则,让组件拥有 prop、event、slot 和 scoped slot,这些东西让组件的交互和扩展变得轻而易举。我们可以构建具备所有特性的组件,而无需修改任何样式或标记。这对于可重用性和高效代码来说非常重要。

构建“Toggle”无渲染组件

构建这个组件很简单,甚至不需要搭建 Vue CLI 项目。

Toggle 组件可以让你在打开和关闭状态之间切换,对于构建开关式组件(例如自定义复选框和任何需要开关状态的组件)来说是非常有用的。

让我们来看看下面的代码,可以将它直接放在 CodePen(https://codepen.io/pen)的 JavaScript 一栏中:

// toggle.js
const toggle = {
  props: {
    on: { type: Boolean, default: false }
  },
  render() {
    return []
  },
  data() {
    return { currentState: this.on }
  },
  methods: {
    setOn() {
      this.currentState = true
    },
    setOff() {
      this.currentState = false
    },
    toggle() {
      this.currentState = !this.currentState
    }
  }
}

这段代码并不完整,它还需要一个模板。因为我们不希望这个组件渲染任何内容,所以必须确保它可以与其他任何组件一起使用。

无渲染组件中的 slot

slot 允许我们在 Vue 组件的开始标记和关闭标记之间放置内容,比如:

<toggle>
  This entire area is a slot.
</toggle>

在 Vue 的单文件组件中,我们可以这样定义一个 slot:

<template>
  <div>
    <slot/>
  </div>
</template>

如果要使用 render() 函数,我们可以这样:

// toggle.js
render() {
  return this.$slots.default
}

我们可以自动将内容放入切换组件中。

使用 scoped slot 传递数据

在 toggle.js 中,methods 对象有一些 on 状态和一些辅助函数。如果我们能让开发人员访问它们,那就太好了。我们目前正在使用 slot,但它不允许我们暴露子组件的任何内容。

因此,我们需要的是 scoped slot。scoped slot 的原理与 slot 完全相同,只是它能够在不触发事件的情况下暴露数据。

我们可以这样:

<toggle>
  <div slot-scope="{ on }">
    {{ on ? 'On' : 'Off' }}
  </div>
</toggle>

div 的 slot-scope 属性对从 Toggle 组件暴露出来的对象进行解构。

在 render() 函数中,我们可以这样做:

render() {
  return this.$scopedSlots.default({})
}

这一次,我们将 $scopedSlots 对象的 default 属性作为方法调用,因为 scoped slot 现在是带有参数的方法。在这种情况下,方法名是 default,因为 scoped slot 没有指定特定的名称,而且它是唯一的 scoped slot。然后,我们传给 scoped slot 的参数就可以在组件中暴露出来。现在,让我们暴露 on 的当前状态和辅助函数。

render() {
  return this.$scopedSlots.default({
    on: this.currentState,
    setOn: this.setOn,
    setOff: this.setOff,
    toggle: this.toggle,
  })
}
使用 Toggle 组件

我们可以在 CodePen 中完成所有这些操作。以下是我们正在构建的内容:

以下是标记:

<div id="app">
  <toggle>
    <div slot-scope="{ on, setOn, setOff }" class="container">
      <button @click="click(setOn)" class="button">Blue pill</button>
      <button @click="click(setOff)" class="button isRed">Red pill</button>
      <div v-if="buttonPressed" class="message">
        <span v-if="on">It's all a dream, go back to sleep.</span>
        <span v-else>I don't know how far the rabbit hole goes, I'm not a rabbit, neither do I measure holes.</span>
      </div>
    </div>
  </toggle>
</div>
  1. 首先,我们对作 scoped slot 中的状态和辅助函数进行解构。

  2. 然后,在 scoped slot 中,我们创建了两个按钮,一个用于打开,另一个用于关闭。

  3. click 方法确保在显示结果之前用户确实按下了按钮。下面是 click 方法的代码。

new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

我们仍然可以从 Toggle 组件传递 props 和触发事件,使用 scoped slot 并不会改变任何内容。

new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

这是一个基本示例,但我们已经能够看到它有多么的强大。我们可以在多个项目中重用这些组件,而不必担心那些讨厌的样式表会妨碍我们。

总结

  • 组件的渲染函数非常强大。

  • 通过构建 Vue 组件来加快运行时间。

  • 组件的 el、template 属性或单文件组件都被编译成渲染函数。

  • 尝试构建更小的组件来提高可重用性。

  • 你的代码不一定需要遵循 S.O.L.I.D.,但它确实是非常好的编码指导原则。

  英文原文

https://css-tricks.com/building-renderless-vue-components/

  课程推荐

推荐极客时间《技术管理实战 36 讲》专栏,前百度最佳经理人刘建国为你解惑团队 leader 的 36 个场景,传授打造高效团队的 13 种方法,分享十年管理的“战地笔记”。

现在订阅,立享限时福利:

福利一:限时优惠价¥45,原价¥68,8 月 25 日恢复原价

福利二:每邀请一位好友购买,你可获得 12 元现金返现,好友可返 6 元。多邀多得,上不封顶,随时提现(提现流程:极客时间 App - 我的 - 分享有赏)


 
前端之巅 更多文章 用实例告诉你如何重构带有坏味道的代码 Web性能分析工具WebpageTest详解 谷歌推出最新AngularJS升级工具,可快速迁移至Angular 在开始React之前,你需要学好这些JavaScript Dart重启!Dart2正式发布,目标成为移动与Web开发主流语言
猜您喜欢 数据结构常见的八大排序算法 聊聊设计模式(2):享元模式 “地震局IT系统虚拟化平台项目”验收成功 R语言入门第17讲:基础绘图(六)------par函数(4) UNION系统的运营与运维