首页 > 解决方案 > 在以编程方式创建的孩子中观看道具更新

问题描述

我使用以下方法创建了孩子:

const ComponentClass = Vue.extend(someComponent);
const instance = new ComponentClass({
    propsData: { prop: this.value }
})
instance.$mount();
this.$refs.container.appendChild(instance.$el);

this.value在父级中更新时,其值在子级中不会改变。我试过看它,但没有用。

标签: vue.js

解决方案


更新:

有一种更简单的方法可以实现这一点:

  • 创建一个<div>
  • 将其附加到您的$refs.container
  • .$mount()在 div 中创建一个新的 Vue 实例
  • 将 div 实例的数据设置为您想要动态绑定的任何内容,从父级获取值
  • 通过 render 函数从 div 的数据中为挂载的组件提供 props
methods: {
  addComponent() {
    const div = document.createElement("div");
    this.$refs.container.appendChild(div);
    new Vue({
      components: { Test },
      data: this.$data,
      render: h => h("test", {
        props: {
          message: this.msg
        }
      })
    }).$mount(div);
  }
}

重要说明: this inthis.$data指的是父级(具有该addComponent方法的组件),而thisinsiderendernew Vue()的是实例。所以,反应链是:parent.$data> new Vue().$data> new Vue().render=> Test.props。我曾多次尝试绕过该new Vue()步骤并Test直接传递一个组件,但还没有找到方法。不过,我很确定这是可能的,尽管上面的解决方案在实践中实现了它,因为<div>其中的new Vue()渲染被它的模板取代,它是Test组件。所以,在实践中,Test是 的直系祖先$refs.container。但实际上,它通过了一个额外的 Vue 实例,用于绑定。

显然,如果您不想在每次调用该方法时都向容器中添加一个新的子组件,您可以div简单地放弃占位符和.$mount(this.$refs.container),但是这样做您将在以后每次调用该方法时替换现有的子组件。

看到它在这里工作:https ://codesandbox.io/s/nifty-dhawan-9ed2l?file=/src/components/HelloWorld.vue

但是,与下面的方法不同,您不能使用父级的值动态覆盖子级的数据道具。但是,如果你想一想,这就是data 应该工作的方式,所以只要使用props你想要绑定的任何东西。


初步答案:

这是我在多个项目中使用过的一个函数,主要用于为地图框弹出窗口和标记创建编程组件,但对于创建组件而不实际将它们添加到 DOM 也很有用,用于各种目的。

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

export function addProgrammaticComponent(parent, component, dataFn, componentOptions) {
  const ComponentClass = Vue.extend(component);
  const initData = dataFn() || {};
  const data = {};
  const propsData = {};
  const propKeys = Object.keys(ComponentClass.options.props || {});

  Object.keys(initData).forEach(key => {
    if (propKeys.includes(key)) {
      propsData[key] = initData[key];
    } else {
      data[key] = initData[key];
    }
  });

  const instance = new ComponentClass({
    // store,
    data,
    propsData,
    ...componentOptions
  });

  instance.$mount(document.createElement("div"));

  const dataSetter = data => {
    Object.keys(data).forEach(key => {
      instance[key] = data[key];
    });
  };

  const unwatch = parent.$watch(dataFn || {}, dataSetter);

  return {
    instance,
    update: () => dataSetter(dataFn ? dataFn() : {}),
    dispose: () => {
      unwatch();
      instance.$destroy();
    }
  };
}

componentOptions是为新实例提供任何自定义(一次性)功能(即:mounted(),观察者,计算,存储,你命名它......)。

我在这里设置了一个演示:https ://codesandbox.io/s/gifted-mestorf-297xx?file=/src/components/HelloWorld.vue

请注意,我不是appendChild故意在函数中执行的,因为在某些情况下,我想在instance不将其添加到 DOM 的情况下使用。常规用法是:

const component = addProgrammaticComponent(this, SomeComponent, dataFn);
this.$el.appendChild(component.instance.$el);

根据您的动态组件的作用,您可能希望.dispose()在父级的beforeDestroy(). 如果你不这样做,beforeDestroy()on child 永远不会被调用。


可能最酷的部分是您实际上不需要将孩子附加到父母的 DOM(它可以放置在 DOM 中的任何位置,并且孩子仍然会响应父母的任何更改,就像它是实际的后代)。他们的“链接”是程序化的,通过dataFn.

显然,这为一系列潜在问题打开了大门,尤其是在摧毁父母而不摧毁孩子方面。因此,您需要非常小心和彻底地进行此类清理。您可以将每个动态组件注册到父级的属性中并将.dispose()它们全部注册到父级的属性中,beforeDestroy()或者给它们一个特定的选择器并在销毁父级之前清除整个 DOM。

另一个有趣的注意事项是,在 Vue 3 中,以上所有内容都不再是必需的,因为大多数核心 Vue 功能(反应性、计算、钩子、侦听器)现在都可以按原样公开和重用,所以你$mount不必组件以访问其“魔法”


推荐阅读