构建“无渲染”Vue组件

Avatar of Samuel Oloruntoba
Samuel Oloruntoba

DigitalOcean 提供适用于旅程每个阶段的云产品。立即开始使用 免费赠送的 200 美元积分!

关于 Vue 有个流行的比喻:Vue 是 React 和 Angular 结合后诞生的孩子。 我一直都有这种感觉。Vue 学习曲线很平缓,难怪那么多人喜欢它。由于 Vue 尽可能地赋予开发者对组件及其实现的控制权,这种观点也导致了今天的主题。

术语无渲染组件指的是不渲染任何内容的组件。在本文中,我们将介绍 Vue 如何处理组件的渲染。

我们还将了解如何使用 render() 函数构建无渲染组件。

您可能需要了解一些 Vue 知识才能充分利用本文。如果您是新手,Sarah Drasner 可以帮您官方文档也是一个非常好的资源。

揭秘 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 })
}

嗯……不太像我们上面看到的。为了理解接下来发生的事情,我们需要谈谈模板编译器。

模板编译器和渲染函数

Vue 组件构建过程的这一部分对于编译和运行 Vue 目前实现的每项优化技术都是必要的。

当模板编译器遇到

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

…它会提取 template 属性并将它的内容编译成 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 组件,您不必导入模板编译器,这样可以避免构建文件大小膨胀。您的组件还经过预优化,可以实现出色的性能和真正轻量级的 JavaScript 文件。

所以… 无渲染 Vue 组件

正如我提到的,术语无渲染组件指的是不渲染任何内容的组件。为什么我们要使用不渲染任何内容的组件呢?

我们可以将其归结为将常见组件功能的抽象作为其自身的组件,并扩展该组件以创建更好、更强大的组件。此外,S.O.L.I.D

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

一个类应该只具有一个目的。

我们可以将这个概念移植到 Vue 开发中,让每个组件都只具有一个责任。

您可能像 Nicky 一样,“切,我知道”。好吧,当然!您的组件可能命名为“password-input”,它肯定渲染一个密码输入。这种方法的问题是,当您想在另一个项目中重用此组件时,可能需要进入组件的源代码来修改样式或标记,以符合新项目的样式指南。

这违反了 S.O.L.I.D 的一个规则,称为开闭原则,该原则规定

一个类或组件(在本例中)应该对扩展开放,但对修改关闭。

这意味着您应该能够扩展它,而不是编辑组件的源代码

由于 Vue 理解 S.O.L.I.D 原则,它允许组件具有 props事件插槽作用域插槽这使得组件的通信和扩展变得轻而易举。然后,我们可以构建具有所有功能而没有任何样式或标记的组件。这对可重用性和高效代码来说非常棒。

构建“Toggle”无渲染组件

这将非常简单。无需设置 Vue CLI 项目。

Toggle 组件将允许您在开启和关闭状态之间切换。它还将提供帮助程序,让您能够执行此操作。它对于构建组件很有用,例如,开启/关闭组件,例如自定义复选框和任何需要开启/关闭状态的组件。

让我们快速创建组件存根:前往 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
    }
  }
}

这非常简陋,还没有完成。它需要一个模板,由于我们不希望此组件渲染任何内容,因此我们必须确保它与任何渲染内容的组件都能配合使用。

该轮到插槽了!

无渲染组件中的插槽

插槽 允许我们将内容放置在 Vue 组件的开始标签和结束标签之间。像这样

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

在 Vue 的单文件组件中,我们可以像这样定义插槽

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

好吧,要使用 render() 函数做到这一点,我们可以

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

我们可以自动将内容放置到 Toggle 组件中。没有标记,什么都没有。

使用作用域插槽将数据发送到树的上层

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

我们想要的是作用域插槽。作用域插槽的工作方式与插槽完全相同,但它们具有一个附加的优势,即拥有作用域插槽的组件能够公开数据而不触发事件。

我们可以做到

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

您在 div 上看到的 slot-scope 属性是解构从 Toggle 组件公开的对象。

回到 render() 函数,我们可以

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

这一次,我们调用 $scopedSlots 对象上的 default 属性作为方法,因为作用域插槽是接收参数的方法。在本例中,方法的名称为 default,因为作用域插槽没有指定名称,它是唯一存在的插槽。传递给作用域插槽的参数随后可以从组件中公开。在我们的例子中,让我们公开 on 的当前状态以及帮助操作该状态的帮助程序。

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

使用 Toggle 组件

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

查看 Pen ZRaYWm by Samuel Oloruntoba (@kayandrae07) on 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. 首先,我们从作用域插槽中解构状态和帮助程序。
  2. 然后,在作用域插槽内,我们创建了两个按钮,一个用于切换当前状态打开,另一个用于关闭。
  3. click 方法只是为了确保在显示结果之前确实按下了按钮。您可以在下面查看 click 方法。
new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})

我们仍然可以从 Toggle 组件传递 props 和触发事件。使用作用域插槽不会改变任何内容。

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

这是一个基本示例,但我们可以看到当我们开始构建诸如日期选择器或自动完成小部件之类的组件时,它可以变得多么强大。我们可以跨多个项目重用这些组件,而不必担心那些讨厌的样式表妨碍。

我们可以做的另一件事是从作用域插槽中公开可访问性所需的属性,并且不必担心使扩展此组件的组件可访问。

总结

  • 组件的渲染函数非常强大。
  • 构建您的 Vue 组件以获得更快的运行时。
  • 组件的eltemplate 甚至单文件组件都将编译成渲染函数。
  • 尝试构建更小的组件以获得更可重用的代码。
  • 您的代码不必是 S.O.L.I.D.,但这是一个非常好的编码方法。

来源