本文假定您对 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 上关注我,我会分享更多我的文章和副项目。
很棒的文章! 但是还有一个功能。 Vue 允许我们通过传递
parent
选项来为手动创建的实例建立父子关系,这意味着我们可以对我们的组件使用provide/inject
:文档在这里以下是来自文章的更新代码:https://codesandbox.io/s/045k690vyw?autoresize=1&module=%2FApp.vue&moduleview=1
这展示了如何使用
provide/inject
来更改手动创建的组件实例的主题。演示的正确链接 https://codesandbox.io/s/045k690vyw?autoresize=1&module=%2FApp.vue
太棒了
超级棒!
我认为有一种方法可以
$mount
而无需使用appendChild()
。 最好的方法是不提供$el
作为选项,只需使用instance.$mount(DESTINATION)
即可。这是更新后的 codesandbox:https://codesandbox.io/s/kk4rxzon33?module=%2FApp.vue
在文章中,我解释了为什么在这种情况下的推荐方法不可行
哦,是的,那是真的,它确实替换了
DESTINATION
元素。 我错过了那部分,抱歉。如果您正在阅读这篇文章,我强烈建议您不要这样做。 相反,您可以简单地将您的普通对象组件传递给
<component :is="someObject">
,然后像往常一样绑定属性和传递子元素。 我认为使用前缀属性和方法表明您正在偏离正轨,应该仔细检查您的工作。这是一个很棒的示例! 但是,如何设置组件实例和父级之间的双向绑定呢?