(译)Vue.js 构建一个"无渲染"组件

面试官:谈谈你理解的Vue无渲染组件?javascript

本身先想一分钟。css

译者注:英语和文笔有限,不对之处欢迎留言斧正!原文地址:css-tricks.com/building-re…html

网上有句话这样来形容Vue,说 “Vue 是 React 和 Angular 的产物”。老实说,我也一直有这种感受。凭借着较低的学习曲线,广受开发人员的青睐和喜好。正是因为Vue提供给开发者自由开放式的组件开发的能力,才有了我今天这篇文章。vue

术语无渲染组件意指不渲染任何内容的组件。本文,咱们将介绍Vue是如何处理组件渲染工做的。java

咱们还将会看到如何使用 render() 函数来构建无渲染组件的。webpack

在阅读本文以前假设你对Vue有必定的了解。若是你是一个新手请先阅读Sarah Drasner's post官方文档也是不错的资源。git

解开Vue如何渲染组件的神秘面纱

Vue提供了不少方法来渲染组件:github

  • 单文件组件。让咱们像写普通HTML文件同样去定义组件
  • Vue 提供的 template 属性。容许咱们使用 JavaScript 的模板字符串来定义组件
  • Vue 提供的 el 属性。告诉Vue查询DOM以获取用做组件模板

你可能据说过(可能很讨厌):归根结底,Vue和它全部的组件都只是JavaScript。我能理解你为何以为咱们编写的HTML和CSS的数量是不对的了。用一个案例来阐明这一点:单文件组件。web

使用单文件组件,咱们能够像这样来定义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只是想让咱们便于管理咱们的页面,样式和其余资源,而构建,编译等工做交给了第三方工具,好比 webpack。

webpack 在检索到 .vue 文件时,将进入编译阶段。期间,CSS会被提取到单独的文件,剩下的内容将编译成 JavaScript。相似下面的代码:

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

额... 其实不彻底像上面的代码。要了解接下来会发生什么,咱们须要先聊聊模板编译器。

模板编译器和渲染函数

对于编译和运行Vue当前实现的每一个优化技术来讲,Vue组件的构建过程这一步是必需的。

当模板编译器遇到下面的代码:

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

首先它会提取模板属性并将其内容编译成 JavaScript,而后将渲染函数添加到组件对象上。反过来讲就是,渲染函数会返回从模板属性内容转换成 JavaScript 后的内容。

这就是上面的模板在渲染函数中的样子:

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

有关渲染函数的更多内容,请参阅官方文档。

如今,当组件对象传递给Vue时,组件对象中的渲染函数会通过一些优化处理后变成一个 VNode(虚拟节点)。VNode会被snabbdom(Vue内部用来管理虚拟DOM的库)接手作进一步的处理。Sarah Drasner 解释了上面渲染函数中的 h 函数。

VNode 是Vue渲染组件的一种方式。顺便说一下,Vue还容许咱们在渲染函数中使用 JSX 语法。

咱们没必要等Vue帮咱们添加渲染函数 — 咱们能够本身定义渲染函数,并且它的优先级是高于 eltemplate 属性的。移步这里去了解渲染函数及其选项

使用 Vue CLI 或其余自定义脚手架工具构建Vue组件,你不用去考虑导入可能会影响构建文件大小的模板编译器。你的组件都是通过了预处理,压缩,优化等。在性能,文件体积方面都有出色的表现。

接下来... 无渲染组件

就像我说的,术语无渲染组件意指不渲染任何东西的组件。为何咱们想要一个无渲染的组件呢?

咱们能够经过无渲染组件建立一个抽象组件。就像 Java 中的抽象类,它自己不会作一些事情,只是提供一些接口方法等让外部使用。后续咱们能够经过不断拓展该组件来实现更好更强大的组件。这也是正是 S.O.L.I.D

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

一个类应该只作一件事儿

咱们能够把这种概念移植到Vue开发中,使每一个组件只作一件事儿。

你可能会像Nicky同样,“是的,我知道。“好的,固然!” 你的组件可能会有一个名叫“password-input”,它确定会呈现一个密码输入框。问题在于,当你想要在其余项目重用此组件时,你可能不得不查看组件源码修改样式或者HTML,以便能和新项目的样式或设计图保持统一。

若是你这样作就破坏了 S.O.L.I.D 原则。也就是开闭原则:

类或者组件,应该对拓展开放,对修改关闭。

意思是,你应该拓展它,而不是修改组件的源代码。

因为Vue了解S.O.L.I.D原则,因此它容许组件具备 props、events、slots、以及 scoped slots ,从而使组件的通讯和拓展变得垂手可得。而后,咱们能够构建具备全部功能的组件,而无需任何样式或HTML。对于编写可重用性和高性能的代码来讲真的很不错。

构建一个 “Toggle” 无渲染组件

这很简单,不须要Vue CLI。

开关组件可让你在开和关之间切换。而且它还提供了一些帮助方法供你使用。它对于构建组件(例如,开/关组件、自定义复选框和任何须要开/关状态的组件)很是有用。

先找到咱们的组件:前往 CodePen的 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
    }
  }
}
复制代码

目前组件代码仍是比较少的,功能还没有完成。它须要一个模板,由于咱们不但愿这个组件展现任何东西,因此咱们必须确保它能适用于任何组件。

暗示插槽!

在无渲染组件中使用插槽

插槽容许咱们在标签体中放置内容。像这样:

<toggle>
  This entire area is a slot.
</toggle>
复制代码

在Vue单文件组件中,咱们能够这样来定义一个插槽:

<template>
  <div>
    <slot/>
  </div>
</template>
复制代码

好了,在开关组件的 render() 函数中咱们能够这样来作:

// toggle.js
render() {
  return this.$slots.default
}
复制代码

在开关组件里咱们能够自由的放置东西了。

使用 Scoped Slots 向外部发送数据

toggle.js 中,methods 方法对象上有一些控制开关状态的方法和一些辅助方法。若是咱们能让开发者调用他们那就太好了。而咱们目前正在使用的插槽没法作到这一点,由于它不容许咱们公开组件中的任何内容。

咱们想要的实际上是 scoped slots。做用域插槽的工做方式跟插槽同样。但对比插槽的优势在于,具备做用域插槽的组件在不触发事件的状况下也能够暴露数据。看下面的代码:

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

div 上的 slot-scope 属性经过对象解构并获取从开关组件透传过来的数据。

回到 render() 函数,咱们这样作:

render() {
  return this.$scopedSlots.default({})
}
复制代码

此次,咱们将 $scopedSlots 对象上的 default 属性做为方法调用。由于做用域插槽是带有一个参数的方法。这种状况下,方法名是默认的,由于咱们没有提供具名插槽,因此它将做为惟一存在的做用域插槽。而后咱们就能够把组件中的方法以做用域插槽参数的形式暴露出去了。在这个栗子中,让咱们暴露 on 的当前状态和操做该状态的一些方法吧:

render() {
  return this.$scopedSlots.default({
    on: this.currentState,
    setOn: this.setOn,
    setOff: this.setOff,
    toggle: this.toggle,
  })
}
复制代码

使用Toggle组件

咱们作的这些操做都在 Codepen 上,下面是截图:

下面是相关的HTML代码:

<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. 首先咱们从做用于插槽中解构出开关状态和一些辅助方法
  2. 而后,咱们在做用域插槽内建立了两个按钮,一个用于切换当前状态,另外一个用于关闭。
  3. 在显示结果以前,click() 方法其实就是代理开关组件内的真正要执行的方法。你能够查看下面的 click 方法:
new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})
复制代码

咱们仍然能够从 Toggle 组件传递 props 和触发事件。做用域插槽不会受到任何影响。

这是一个基础的示例,但咱们能够看到,当咱们开始构建日期选择器或自动完成提示等组件时,这种作法是很是适用且强大的。咱们能够在多个项目重复使用这些组件,而没必要担忧那些讨厌的样式妨碍咱们。

咱们能够作的另外一件事是从做用域插槽中公开可访问的属性而没必要担忧拓展组件后的访问性问题。

总结

  • 组件的渲染函数很是强大
  • 快速构建你的Vue组件
  • 组件的 eltemplate 或者单文件组件都会被编译成渲染函数
  • 尝试构建更小的组件以得到更多可重用的代码
  • 你的代码不必定是S.O.L.I.D,但务必准守一个好的编码风格

参考资料

最后,下面是我维护的一个Q群,喜欢Vue的同窗,欢迎扫码进群哦,让咱们一块儿交流学习吧。也能够加我我的微信:G911214255 ,备注 掘金 便可。

Q1769617251
相关文章
相关标签/搜索