在 Vue.js 中动态实例化组件

Posted

技术标签:

【中文标题】在 Vue.js 中动态实例化组件【英文标题】:Dynamically instantiating a component in Vue.js 【发布时间】:2019-04-13 11:19:56 【问题描述】:

在this tutorial 之后,我正在尝试以编程方式在我的页面上创建组件的实例。

主要的sn-p是这样的:

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

但是我得到两个错误:

    我尝试实例化的组件包含对存储的引用,这些引用不起作用:“TypeError: Cannot read property 'state' of undefined”

    对于 sn-p (this.$refs.container.appendChild(instance.$el)) 的最后一行,我收到此错误:“Uncaught TypeError: Cannot read property 'container' of undefined”

我真的不知道如何解决这个问题,如果有 Vue.js 方面的强者可以给我一些提示,说明我为什么会遇到这些错误并解决它们,那将是非常棒的。

【问题讨论】:

你有引用 container 的元素吗? <div ref="container"></div> 很好,不幸的是,在我的模板中添加<div ref="container"></div> 后,我仍然得到相同的两个错误。 你能发布一个运行的例子吗? 我的整个代码都暴露了。另外,我不知道如何在 Jsfiddle 之类的网站上上传完整的 Webpack/Vue 项目,这可能吗? 我认为当您调用$refs 时,dom 尚未创建。相反,将它放在导出模块中的方法中,就像您引用的教程一样。 codesandbox 【参考方案1】:

我沿着这条路走,遵循上面所有的例子,甚至是这个:https://css-tricks.com/creating-vue-js-component-instances-programmatically/

虽然我走得很远,而且它可以工作(我用这种方法制作了很多组件),但至少就我而言,它有缺点。例如我同时使用 Vuetify,动态添加的组件不属于外部表单,这意味着虽然本地(每个组件)验证有效,但表单没有收到整体状态。另一件不起作用的事情是禁用表单。随着更多的工作,将表单作为父属性传递,其中一些工作开始了,但是 删除 组件呢。那并不顺利。虽然它们是不可见的,但它们并没有真正被删除(内存泄漏)。

所以我改用渲染函数。它实际上要容易得多,有据可查(Vue 2 和 Vue 3),而且一切正常。我也从这个项目中得到了很好的帮助:https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/

基本上,要动态添加函数,只需实现render() 函数,而不是使用模板。工作起来有点像 React。你可以在这里实现任何逻辑来选择标签、选项,一切。只需返回它,Vue 就会构建 shadow-DOM 并保持真实 DOM 处于最新状态。

这里的方法似乎直接操作 DOM,我很高兴我不再需要这样做。

【讨论】:

【参考方案2】:

它通过将“this”分配给属性“parent”来工作。通过设置父级,您还可以访问新实例中的 $store。 (前提是“this”是另一个 Vue 实例/组件,当然已经可以访问 store)

new (Vue.extend(YourNewComponent))(
    parent: this,
    propsData: 
        whatever: 'some value',
    ,
).$mount(el.querySelector('.some-id'))

如果您不需要对父级的引用,则可以将“父级:this”留出。

重要提示:当以这种方式在页面上安装许多(如 500 多个)项目时,您将获得巨大的性能影响。最好只通过 props 给新组件提供必要的东西,而不是给它整个“this”对象。

【讨论】:

当我分配 parent:this 而不是分别分配 store、router、i18n 时,我得到了这个 vue 警告,“避免直接改变 prop,因为每当父组件重新渲染时,该值都会被覆盖。 " ...即使我使用计算属性来使用内部道具中的信息...您知道可能导致这种情况的原因吗? 就像它说的那样,您可能正在某处“设置”该值(覆盖它)。而且您必须实际更新外部值(源),而不是您可以从内部访问的值。对于内部价值,它是用于使用/查看/显示它,而不是更新它。所以在源代码中更新它,或者如果这不起作用,你可以克隆值并更新它?取决于你想要达到的目标 我知道,但我没有在任何地方设置它,它只是为了显示......我还使用返回道具值的计算属性,所以基本上我没有在任何地方使用它,但在该属性中...问题是仅在我发送父级后才会出现警告...如果我发送存储,路由器等。单独警告不会出现:/ 我不知道您的代码,但有时即使在“计算”您实际修改它的值时。首先要做的可能是将我们计算函数中的传入数据“克隆”成一个真正的新数据。此外,我更新了我的答案,因为以前的答案很旧,我已经找到了更好的方法。所以试试吧? 我暂时放弃了发送父母的想法,我只是单独发送我需要的东西,我有更高优先级的任务,我稍后会回来......但是谢谢为您的帮助...【参考方案3】:

这是在 Vue.js 中实例化组件的另一种方式,您可以使用两个不同的根元素。

// Instantiate you main app
var app = new Vue(
  el: '#app',
  data: 
    message: 'Hello Vue!'
  
)

//
// Then instantiate your component dynamically
//

// Create a component or import it.
const Hello = 
  props: ['text'],
  template: '<div class="hello"> text </div>',
;

// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);

// Use componentClass to instantiate your component.
const vm = new HelloCtor(
  propsData: 
    text: 'HI :)'
  
)
// then mount it to an element.
.$mount('#mount');


【讨论】:

【参考方案4】:

1) 由于您手动实例化该组件并且它不属于您的主应用程序的组件树,因此商店不会从您的根组件自动注入到其中。实例化组件时,您必须手动将商店提供给构造函数..

import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";

let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass( store );

2) 在 Vue Single File Component (SFC) 中,默认导出之外的 this 不引用 Vue 实例,因此您无权访问 $refs 或任何其他 Vue 实例属性/方法。要访问 Vue 实例,您需要将此行 this.$refs.container.appendChild(instance.$el) 移动到默认导出中的某个位置,例如在 mounted 钩子中或您的 methods 之一中。

请参阅此CodeSandbox,了解如何处理此问题的示例。

【讨论】:

这似乎有效!知道如何将组件注入与我们所在的组件不同的组件吗?所以不是this.$refs.container.appendChild(ProjectRowInstance.$el);,而是anotherComponent.$refs.container.appendChild(ProjectRowInstance.$el);。有没有办法做到这一点? @drake035 您应该能够使用$root$parent 向上遍历组件树,并使用$refs 向下遍历组件树,如the docs 中所述。例如,请参阅this jsfiddle。 非常感谢您的帮助!

以上是关于在 Vue.js 中动态实例化组件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Vue.js 中将实例化的 Vue 组件作为道具传递?

Vue.js:以编程方式实例化功能组件

将 props 传递给 Vue-router 实例化的 Vue.js 组件

第二节:Vue实例化

vue构造器以及实例属性

动态实例化 Web 组件的方法之间的差异