Vue.js 在 DOM 树突变后挂载组件以添加一个 vue 组件

Posted

技术标签:

【中文标题】Vue.js 在 DOM 树突变后挂载组件以添加一个 vue 组件【英文标题】:Vue.js mount component after DOM tree mutation to add a vue component 【发布时间】:2018-07-27 23:11:58 【问题描述】:

我有一个用例(如下),我需要 mount(如果这是正确的术语)一个通过 jQuery 插入 DOM 的 Vue.js 组件模板,我可以设置一个 Mutation Observer 或对某些响应突变发生时触发的事件。

我正在使用 Vue.js v2

下面是我整理的一个简单例子来说明这一点:

直播 jsFiddle https://jsfiddle.net/w7q7b1bh/2/

下面的 html 包含两个组件的 inlined-templates

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div id="app">
  <!-- The use of inline-template is required for my solution to work -->
  <simple-counter inline-template>
    <button v-bind:style="style" v-on:click="add">clicks:  counter </button>
  </simple-counter>
  <simple-counter inline-template>
    <button v-on:click="counter += 1"> counter </button>
  </simple-counter>
</div>

<button id="mutate">Mutate</button>

js:

// simple counter component
Vue.component('simple-counter', 
  data: function() 
    return 
      counter: 0,
      style: 
        color: 'red',
        width: '200px'
      
    
  ,
  methods: 
    add: function() 
      this.counter = this.counter + 1;
      this.style.color = this.style.color == 'red' ? 'green' : 'red';
    
  
)

// create the Vue instance
var initV = () => new Vue(
  el: '#app'
);

// expose the instance for later use
window.v = initV();

// click handler that will add a new `simple-counter` template to the Vue.el scope
$('#mutate').click(function()
    $('#app').append(`  <div is="simple-counter" inline-template>
    <button v-bind:style="style" v-on:click="add">click to add: <span class="inactive" v-bind:class=" active: true "> counter </span></button></div>`)
    // do something after the template is incerted
    window.v.$destroy()
    window.v = initV(); // does not work

)

如代码中所述,销毁重新实例化 Vue 实例不起作用,我理解为什么,组件的模板在第一次 Vue 实例化时更改为最终的 HTML,当您尝试第二次实例化时,模板不存在,组件不存在mounted

我希望能够在突变后找到新添加的组件并仅安装那些,这可能吗?以及如何?

更新: 我能够通过将 el 设置为 DOM 的特定变异部分而不是整个 #app 树实例化一个新的 Vue 实例来找到一种方法:

$('#mutate').click(function()
    var appended = 
            $(`
       <div is="simple-counter" inline-template>
         <button v-bind:style="style" v-on:click="add">
           click to add:  counter 
         </button>
       </div>`
      ).appendTo($('#app'));

    var newV = new Vue(el: appended[0]);
);

似乎有效,但看起来也很丑陋,我不确定这可能会产生什么其他影响..

用例:

我正在研究一种为名为 Adob​​e Experience Manager (AEM) 的 CMS 编写 Vue.js 组件的方法。

我使用 inlined-template 编写我的组件,这让我利用了 SEO 以及使用另一种称为 HTL 的模板语言进行服务器端渲染的优势。

AEM 创作的工作方式是,当(通过对话框)编辑组件时,该特定组件会在服务器端重新渲染,然后注入回 DOM 以替换旧组件,所有这些都通过 Ajax 完成和 jQuery(无浏览器刷新)。

这是一个例子

AEM 组件模板: &lt;button&gt;$properties.buttonTitle&lt;/button&gt;

作者可能会这样做:

    作者访问创作页面 打开按钮组件对话框进行编辑 将 buttonTitle 更改为“新按钮标题” 保存

保存后,发送一个 ajax,组件 HTML 在服务器上重新渲染,返回的是新的 HTML。该 HTML 现在通过 jQuery 替换旧的 HTML(改变 DOM)

这对于静态组件来说很好,但是如果这是一个 Vue.js 组件,我如何在保持其他组件挂载的同时动态挂载它。

一个简单的解决方案是刷新页面......但这只是糟糕的体验......必须有更好的方法。

【问题讨论】:

将新部分实例化为新的 Vue 似乎是正确的方法,但似乎绑定不起作用,因为它们在另一个 Vue 中。但是,他们可能会在作用域插槽中。 有没有办法手动挂载组件,然后将它们添加到 Vue 实例中? 我真的不知道。让其他东西修改 DOM 然后告诉 Vue 接管这些更改是非常不符合 Vue 的。如果您的服务器只提供不同的数据并且前端可以进行 DOM 更新,那就更好了。 我完全同意,这只会发生在创作界面上。对于最终用户来说,这永远不会发生。我认为现在只用更新的 DOM 创建一个新的 Vue 实例就足够了。 可能会有帮助:css-tricks.com/… 【参考方案1】:

感谢@liam,我能够找到合适的解决方案来解决我的问题

在使用 HTML 模板改变 DOM 后,保留对该模板的父元素的引用

例如:

var $template = $('<div is="simple-counter" inline-template> ..rest of template here.. <div>').appendTo('#app') // app is the Vue instance el or a child of it

现在您可以创建组件的新实例并将$template 添加到它作为el 属性

如果我的组件是:

var simpleCounterComponent = Vue.component('simple-counter', 
  data: function() 
    return 
      counter: 0,
      style: 
        color: 'red',
        width: '200px'
      
    
  ,
  methods: 
    add: function() 
      this.counter = this.counter + 1;
      this.style.color = this.style.color == 'red' ? 'green' : 'red';
    
  
)

我能做到:

var instance = new simpleCounterComponent(
  el: $template.get(0) // getting an HTML element not a jQuery object
);

这样,那个新添加的模板就变成了一个Vue组件

根据问题看一下这个小提琴的工作示例:

https://jsfiddle.net/947ojvnw/11/

【讨论】:

以防万一有人觉得它有用,这里有一个不使用inline-template的版本:jsfiddle.net/9hL8zvx7/2【参考方案2】:

在运行时生成的 HTML 中实例化 Vue 组件的一种方法是:

var ComponentClass = Vue.extend(
    template: '...',
);
var instance = new ComponentClass(
    propsData:  name: value ,
);
instance.$mount('#uid'); // HTML contains <... id="uid">
...
instance.$destroy(); // if HTML containing id="uid" is dropped

更多在这里(我不隶属于这个网站)https://css-tricks.com/creating-vue-js-component-instances-programmatically/

【讨论】:

虽然不是我正在寻找的完整答案,但它帮助我找到了我正在寻找的答案,我会尽快发布该答案

以上是关于Vue.js 在 DOM 树突变后挂载组件以添加一个 vue 组件的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js 实例方法

Vue.js 实例方法

Vue.js 3.0 组件是如何渲染为 DOM 的?

DBVisualizer 查询语法以添加一系列值

组件的生命周期

Vue组件