刚开始写组件的时候,可能会出现这样的问题:
<div id="root"> <table> <tbody> <row></row> <row></row> <row></row> </tbody> </table> </div> <script type="text/javascript"> Vue.component('row',{ template:'<tr><td>this is a row</td></tr>' }) var vm = new Vue({ }) </script> </body> 复制代码
他的运行结果是这样:
那么为什么没有将模板内的<tr>显示在页面。
因为即使给组件命名,完成这样一个全局子组件,我们也需要创建一个实例(根组件)并通过外部盒子的id让vue接管该盒子(将根和html挂载上),不能直接使用
那么我们在实例vm中加上el:"#root"
。问题就解决了!
当我们加上了对id为root元素的定位,想要的效果出来了,但是我们再看一下控制台:
这里的组件 <row> 跳出 <tbody> 和 <table> 在同级了。H5编码规范中规定:在<table>内有<tbody>,而<tbody>内又必须为 <tr>即使在 <table> 中不使用 <tbody> ,直接使用标签,也是不可行的。
语法:<标签名 is="组件名">
可以做到在不改变html规范结构的同时,完成对标签的替换。
这里只对HTML部分修改:
<div id="root"> <table> <tbody> <tr is="row"></tr> <tr is="row"></tr> <tr is="row"></tr> </tbody> </table> </div> 复制代码
在渲染时, <tr> 标签会视为组件 "row" ,并且不破坏html规范。
这时的控制台:
除了在 <tbody> 内 <tr> 中可以使用is属性,类似的,还可以在修改 <ul> 或 <ol> 内 <li> 中使用:
<div id="root"> <table> <ul> <li is="row"></li> <li is="row"></li> <li is="row"></li> </ul> </table> </div> 复制代码
效果也ok
与上文的html部分相同,仅仅修改子组件的部分:
我们设想使用data属性中的content来存储这段字符串并在模板中使用。
<div id="root"> <table> <ul> <li is="row"></li> <li is="row"></li> <li is="row"></li> </ul> </table> </div> <script type="text/javascript"> Vue.component('row',{ data:{ content:"this is a row" }, template:'<tr><td>{{content}}</td></tr>' }) var vm= new Vue({ el:"#root" }) </script> 复制代码
然而
在根组件中在data内直接定义没问题,但是在非根组件中,定义要求data内必须是函数,同时要求返回一个对象,在对象中包含需要的数据即可。
html部分不变,JS代码修改为:
<script type="text/javascript"> Vue.component('row',{ data:function(){ return{ content:"this is row" } }, template:'<tr><td>{{content}}</td></tr>' }) var vm= new Vue({ el:"#root" }) </script> 复制代码
再重复一下,data内为函数,所需数据放入return中。
至于原因:在这个程序中,vm作为一个根组件,只会被调用一次,但是子组件会被调用多次(本程序调用了三次),为了使每个子组件的数据独一性,通过函数多次返回使每个子组件有独立的存储,避免影响。
用于获取DOM结点
我们先写一个简单的例子:点击跳出警告
<body> <div id="root"> <div @click="handleclick">hello world</div> </div> <script type="text/javascript"> var vm = new Vue({ el:"#root", methods:{ handleclick:function(){ alert("click") } } }) </script> </body> 复制代码
我们想实现:当点击 “hello world” 这段话的时候,跳出的警告也是 “hello world”
我们首先对DOM部分进行修改:在包含 "hello world" 这句话的 div 中添加:ref="hello"
,意为给这个 div 添加一个名为 “hello” 的引用。
<div id="root"> <div ref="hello" @click="handleclick">hello world</div> </div> 复制代码
然后我们就可以在实例中定义点击函数了:
<script type="text/javascript"> var vm = new Vue({ el:"#root", methods:{ handleclick:function(){ alert(this.$refs.hello.innerHTML) } } }) 复制代码
修改部分意思是:在该实例中所有的引用中,找到名为 hello 的引用内的 html。(其实也就是找到了 div相应的DOM节点)
我们实现一个点击递加效果,其中,使用 data 中的 number 作为初始。
<body> <div id="root"> <counter></counter> <counter></counter> </div> <script type="text/javascript"> Vue.component('counter',{ template:'<div @click="handleclick">{{number}}</div>', data:function(){ return{ number:0 } }, methods:{ handleclick:function(){ this.number++ } } }) var vm = new Vue({ el:"#root" }) </script> </body> 复制代码
这里点击相同的组件但是出现不同的递增再次强调了上面要求组件中使用function并return的作用(防止多次使用导致数据紊乱)。
然后我们开始编写递加并求和的代码:
<div id="root"> <counter @change="handlechange"></counter> <counter @change="handlechange"></counter> <div>{{total}}</div> </div> <script type="text/javascript"> Vue.component('counter',{ template:'<div @click="handleclick">{{number}}</div>', data:function(){ return{ number:0 } }, methods:{ handleclick:function(){ this.number++; this.$emit('change') } } }) var vm = new Vue({ el:"#root", data:{ total:0 }, methods:{ handlechange:function(){ ... } } }) </script> 复制代码
我们看,在子组件被点击实现+1的时候,向外触发事件 "change"。这时我们让父组件监听事件 "change" ,当发现这个事件被触发时,再向上触发父组件的 "handlechange" 函数。这样就实现了子组件事件触发后引起父组件变化。
<body> <div id="root"> <counter ref="one" @change="handlechange"></counter> <counter ref="two" @change="handlechange"></counter> <div>{{total}}</div> </div> <script type="text/javascript"> Vue.component('counter',{ template:'<div @click="handleclick">{{number}}</div>', data:function(){ return{ number:0 } }, methods:{ handleclick:function(){ this.number++; this.$emit('change') } } }) var vm = new Vue({ el:"#root", data:{ total:0 }, methods:{ handlechange:function(){ this.total=this.$refs.one.number+this.$ref.two.number; } } }) </script> </body> 复制代码
var 组件名 ={ props:['...'] template:'...', data:{ ... } } 复制代码
由于创建的是局部子组件,所以需要在根组件中添加components属性声明此组件。
父组件通过属性向子组件传值,下面是一个父向子传参的栗子:
<div id="root"> <counter :counter="1"></counter> </div> <script type="text/javascript"> var counter = { props:['counter'], template:'<div>{{counter}}</div>', } var vm = new Vue({ el:"#root", components:{ counter:counter } }) </script> 复制代码
通过父组件中绑定的变量 counter=‘1’,将父组件中的counter值传给子组件,然后子组件使用props进行接收并使用在子组件模板中。
咱们继续使用上面实现点击递加的例子 :
虽然效果实现了,但是右侧显示警告:不要直接修改父组件传递来的数据。这里需要提出一个概念:Vue中有单向数据流:父组件可以随意向子组件传递参数并任意修改,但是子组件不能修改从父组件传递的数据,因为父组件的数据可能不止这个子组件在使用,擅自修改可能会导致其他子组件异常。所以我们将父组件中的数据复制到一个变量中(做个替身),然后我们操作子组件中这个变量即可。
最终形式如下:
<body> <div id="root"> <counter :counter="1"></counter> </div> <script type="text/javascript"> var counter = { props:['counter'], data:function(){ return { number:this.counter } }, template:'<div @click="handleclick" >{{number}}</div>', methods:{ handleclick:function(){ this.number++ } } } var vm = new Vue({ el:"#root", components:{ counter:counter } }) </script> </body> 复制代码
接下来我们利用父子传参知识优化一下学习ref那里的点击递增并求和的功能。
html中添加盒子、根组件添加total属性与上面相同。本次重点在于:子组件被点击向外触发事件的时候同时传递出参数。
子组件中的点击函数变为
methods:{ handleclick:function(){ this.number++; this.$emit("inc",1); } } 复制代码
将事件 “inc” 和参数 “1” 传给父组件。
父组件监听并触发事件 "handleinc" ,然后我们设计事件 "handleinc":step用来接收参数并用于函数中。handleinc:function(step){ this.total+=step; } 复制代码
最终代码:
<body> <div id="root"> <counter :counter="1" @inc="handleinc"></counter> <counter :counter="1" @inc="handleinc"></counter> <div>{{total}}</div> </div> <script type="text/javascript"> var counter = { props:['counter'], data:function(){ return { number:this.counter } }, template:'<div @click="handleclick" >{{number}}</div>', methods:{ handleclick:function(){ this.number++; this.$emit("inc",1); } } } var vm = new Vue({ el:"#root", data:{ total:2 }, components:{ counter:counter }, methods:{ handleinc:function(step){ this.total+=step; } } }) </script> </body> 复制代码