- A+
一.前言
有原生js学习基础的都知道原生js是怎么动态创建节点并且绑定事件的,于是我在vue项目中使用原生js的形式动态创建了一个节点并且绑定了一个点击事件,但是,在浏览器中却发现这个点击事件失效了,下面就来探究一下该怎么样实现这个需求
二.原生js动态创建节点并绑定事件失效
<template> <div> <div id="app2"> <div > <div @click="fn" >点击我查看事件</div> </div> </div> <button @click="createNode">创建新的节点</button> </div> </template> <script> export default { data() { return { msg:'hello world' } }, methods: { fn(){ console.log(this.msg); }, createNode(){ var oDiv=document.createElement('div'); oDiv.setAttribute('class','wrapper') oDiv.innerHTML='<div @click="fn">点击我查看事件</div>'; var app2=document.querySelector('#app2'); app2.appendChild(oDiv); } }, } </script> <style lang=""> </style> 复制代码
上面的代码中我给按钮绑定了一个事件:每当点击这个按钮,就使用原生的js动态创建一个类名为wrapper
的节点,并且对它的子节点绑定了一个点击事件fn
,然后插入到id
为app2
的节点下
但是在浏览器中发现,动态创建的节点的点击事件是失效的,而一开始就绑定了同样事件的节点的事件却又是有效的
这是什么原因呢?
具体的原因我还不是特别的清楚,但是这可能是与vue周期与编译有关,其实在使用vue就是为了减少对DOM的操作,以数据来驱动试图,为什么要在vue中折腾DOM呢?
那么vue中怎么实现类似动态插入一个节点并且绑定事件的需求呢?
三.利用组件构造器实现动态添加一个节点并绑定事件
vue.extend()
是一个构造器,用来创建一个子类,参数是一个包含组件选项的对象
下面介绍vue.extend()
的使用方式
第一种:从外部引入节点组件
1.将需要动态添加的节点以及它所绑定的事件以组件的形式,先事先写好
新建一个组件文件NewComponent.vue
<template> <div> <!-- 将要动态添加的节点 --> <button @click="fn">点我显示信息</button> </div> </template> <script> export default { data() { return { msg:'hello world' } }, methods: { fn:function(){ console.log(this.msg); } }, } </script> <style lang=""> </style> 复制代码
2.在你想要动态添加一个节点的那个组件中引入NewComponent.vue
<template> <div id='app2'> <div id="mount-point"><h1>从外部引入新的节点</h1></div> <button @click = "addNode">添加节点</button> </div> </template> <script> //引入将要动态添加节点所在的组件 import Append from './NewComponent.vue'; import Vue from 'vue'; export default { data(){ return{ } }, methods:{ addNode(){ //创建一个构造器 var Profile = Vue.extend(Append); // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point'); } }, } </script> <style lang=""> </style> 复制代码
第二种:在内部创建节点组件
<template> <div id='app2'> <div id="mount-point"><h1>在本组件中动态创建新的节点</h1></div> <button @click = "addNode">添加节点</button> </div> </template> <script> import Vue from 'vue'; export default { data(){ return{ } }, methods:{ addNode(){ // 创建构造器 var Profile = Vue.extend({ template: '<button @click="fn">内部创建的节点组件</button>', data: function () { return { msg:'hello world' } }, methods:{ fn(){ console.log(this.msg) } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point') } }, } </script> <style lang=""> </style> 复制代码
上面两种方式动态创建已经绑定事件的节点组件,在浏览器中运行,当点击"添加节点"按钮,便将节点所在的组件,挂载到id="mount-point"
的div
中,注意这里的挂载是会把id="mount-point"
的div
给完全的替换掉,然后你再点击新创建的节点上绑定的事件,是可以触发的
新动态添加的节点会覆盖被挂载的节点,那么如果我想要保留被挂载的节点怎么办呢?
我们可以采用不直接挂载,采用间接挂载的方式,在原先想要挂载的那个挂载点的内部,使用原生js动态创建一个新节点,将节点组件挂载到这个节点上
<template> <div id='app2'> <div id="mount-point"><h1>在本组件中动态创建新的节点</h1></div> <button @click = "addNode">添加节点</button> </div> </template> <script> import Vue from 'vue'; export default { data(){ return{ } }, methods:{ addNode(){ //手动创建节点,实现间接挂载 var mpNode=document.createElement('div'); mpNode.setAttribute('id','mp'); var pnode=document.querySelector("#mount-point"); pnode.appendChild(mpNode); // 创建构造器 var Profile = Vue.extend({ template: '<button @click="fn">内部创建的节点组件</button>', data: function () { return { msg:'hello world' } }, methods:{ fn(){ console.log(this.msg) } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mp') } }, } </script> <style lang=""> </style> 复制代码
四.组件构造器创建的组件与外部组件之间怎么传值
使用组件构造器创建组件是解决了原生js创建DOM节点,绑定的事件失效的问题,那现在问题又来了,新创建的组件和原来组件之间怎么传值呢?
外部组件传到新创建组件
-
新创建的组件用
props
将外部传进来的参数存起来 -
新创建的组件在挂载时以对象的方式传入参数,传入的值存在
propsData
中
<template> <div id='app2'> <div id="mount-point"><h1>在本组件中动态创建新的节点</h1></div> <button @click = "addNode">添加节点</button> </div> </template> <script> import Vue from 'vue'; export default { data(){ return{ } }, methods:{ addNode(){ //手动创建节点,实现间接挂载 var mpNode=document.createElement('div'); mpNode.setAttribute('id','mp'); var pnode=document.querySelector("#mount-point"); pnode.appendChild(mpNode); // 创建构造器 var Profile = Vue.extend({ template: '<button @click="fn">内部创建的节点组件</button>', data: function () { return { msg:'hello world' } }, props:['str'], methods:{ fn(){ console.log(this.str) } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile({ propsData:{str:'你好呀'} }).$mount('#mp') } }, } </script> <style lang=""> </style> 复制代码
新创建组件传到外部组件
对于新创建组件传到外部组件,目前我尚未查到有效的方法,希望有知道的小伙伴可以留言告诉我
写到这里,不知大家有没有觉得这样创建一个新的节点很麻烦,至少在数据处理上不是很方便
下面我提供另一种思路来实现动态创建节点
五.利用数组以及v-for
实现动态创建节点
<template> <div id='app2'> <div v-for="(v,i) in data" v-bind:key="i"> <input type="text" placeholder="type it" v-model="data[i]"><button @click="del()">取消</button> </div> <button @click = "createNode">创建表单input节点</button> <button @click = "getdata">获取所有input值</button> </div> </template> <script> import Vue from 'vue'; export default { data(){ return{ data:[], index:0 } }, methods:{ getdata(){ console.log(this.data); }, del(){ event.currentTarget.parentNode.style.display='none'; }, createNode(){ this.index++; console.log(this.index); var str="请输入选项" var ele=str+this.index; this.data.push(ele); } }, } </script> <style lang=""> </style> 复制代码
效果:
上面的代码,一开始定义一个空的数组,每点击一次添加按钮,就往数组中添加一个数,数组中有多少个数,就遍历该数组,显示相应数量的节点,然后对应双向数据绑定数组中的值,如果要获取所有表单里面的值,直接获取this.data
即可
六.总结
我们实现一个需求时,我们可以尝试多种方法,但是,有时你会发现,你尝试的方法只解决了其中的某个点,这是就不得不去尝试新的方法,直到实现我们的需求