以编程方式创建 Vue.js 组件实例

Avatar of Kushagra Gour
Kushagra Gour

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

本文假定您对 Vue.js 框架以及如何在其中创建组件有基本的了解。 如果您是 Vue 的新手,那么这个 CSS-Tricks 系列 是一个不错的起点。

我一直在做一个 Vue.js 项目,它需要以编程方式创建组件的能力。 通过以编程方式,我的意思是您完全从 JavaScript 创建和插入组件,而无需在模板中编写任何内容。 本文旨在说明如何在模板中使用组件的不同方面,例如实例化、道具传递、插槽、挂载,转换为 JavaScript 代码。

通常,如果您使用推荐的 单文件组件 样式,您将拥有一个像这样的 Button 组件

<template>
 <button :class="type"><slot /></button>
</template>
<script>
 export default {
   name: 'Button',
   props: [ 'type' ],
 }
</script>

要在另一个组件中使用它,您所要做的就是导入 Button 组件并在模板中插入其标签

<template>
 <Button type="primary">Click me!</Button>
</template>
<script>
 import Button from 'Button.vue'
 export default {
   name: 'App',
   components: { Button }
 }
</script>

在我的情况下,我不知道要在模板中插入哪个组件,也不知道要插入到哪里。 此信息仅在运行时可用。 因此,我需要一种方法来动态地为任何传递的组件创建组件实例,并在运行时将其插入 DOM 中。

创建实例

我想到的第一个创建给定组件的动态实例的想法是将其传递给 new,它会给我实际的实例。 但是,如果您仔细观察上述任何组件代码中的 script 块,它们都导出一个简单的对象,而不是一个类(构造函数)。 如果我这样做

import Button from 'Button.vue'
var instance = new Button()

…它失败了。 我们需要一个类。 或者,简单来说,一个构造函数。 实现此目的的方法是将组件对象传递给 Vue.extend 以创建 Vue 构造函数的子类。 现在,我们可以使用 new 关键字从它创建一个实例

import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()

好极了! 第一步完成了! 现在我们需要将其插入 DOM 中。

插入 DOM

每个 Vue 实例都有一个名为 $mount 的方法,它将组件实例挂载到您传递给它的元素上(即它将传递的元素替换为组件实例)。 这不是我想要的行为。 我想将我的组件实例插入某个 DOM 元素中。 也有一种方法可以做到这一点。 从官方文档

如果未提供 elementOrSelector 参数,则模板将被渲染为一个脱字符号元素,您需要使用本机 DOM API 将其插入文档本身。

这就是我所做的

import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)

在上面的代码中需要注意几件事。

首先,$refs 是在 Vue.js 中获取 DOM 元素引用的推荐方法。 您在要引用的 DOM 元素上指定一个属性(在本例中为 <div ref="container"></div>),然后该元素在组件的 $refs 属性上设置的键上可用。

其次,要从 Vue 组件实例获取本机 DOM 元素引用,可以使用 $el 属性。

将道具传递给实例

接下来,我必须将一些道具传递给我的 Button 实例。 特别是 type 道具。 Vue 构造函数接受一个选项对象,我们可以使用它来传递和初始化相关内容。 对于传递道具,有一个名为 propsData 的键,我们可以像这样使用它

import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass({
    propsData: { type: 'primary' }
})
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)

我们快完成了,只剩下一小部分了。 使用普通的模板方法,我们使用按钮如下:<Button>Click me!</Button>;。 我们只需在标签之间写下内部文本,它就会借助 slot 在最终的按钮标签内呈现。 但是现在我们该如何传递它呢?

设置插槽

如果您在 Vue.js 中使用过插槽,您可能知道插槽在任何实例的 $slots 属性上都是可访问的。 并且如果未使用命名插槽,则插槽以数组形式在 $slots.default 上可用。 这就是我们要修改实例以设置按钮的内部文本的确切键。 请记住,这需要在挂载实例之前完成。 请注意,在我们的例子中,我们只在插槽中放置了一个简单的字符串,因为这是我们所需要的。 但是,您可以使用 createElement 函数以虚拟节点或 VNode 的形式将其传递给它更复杂的 DOM。 您可以 在 Vue.js 文档中了解如何创建虚拟节点。 感谢 Vue 核心团队成员 Rahul 的建议,这是一种非常有效的方法。

import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass({
    propsData: { type: 'primary' }
})
instance.$slots.default = [ 'Click me!' ]
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)

最终演示

查看演示

希望您喜欢这篇文章。 在 Twitter 上关注我,我会分享更多我的文章和副项目。