初学时会忽略的定位
刚开始写组件的时候,可能会出现这样的问题:
<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"。问题就解决了!
一个table小bug
当我们加上了对id为root元素的定位,想要的效果出来了,但是我们再看一下控制台:

原因
H5编码规范中规定:在<table>内有<tbody>,而<tbody>内又必须为 <tr>即使在 <table> 中不使用 <tbody> ,直接使用标签,也是不可行的。
解决方法:is属性
语法:<标签名 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

一个不要犯data的错误
与上文的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作为一个根组件,只会被调用一次,但是子组件会被调用多次(本程序调用了三次),为了使每个子组件的数据独一性,通过函数多次返回使每个子组件有独立的存储,避免影响。
ref 引用
用于获取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节点)
ref在组件中的使用
我们实现一个点击递加效果,其中,使用 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的作用(防止多次使用导致数据紊乱)。
然后我们开始编写递加并求和的代码:
- 接下来我们实现对两个组件中数字的求和: 我们在子组件 "counter" 中的点击事件中,添加一个向外触发函数

- 接下来修改子组件和HTML部分:
<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" 函数。这样就实现了子组件事件触发后引起父组件变化。
- 然后我们在父组件的方法中设计" handlechange"方法(其实就是求和)。这时,我们需要获取两个子组件的值,我们使用ref来引用。

- 最后我们使用获取的数值,通过 ref 中的 one和 two内的 number获取,如果没有 number 则获得整个子组件。

- 最终完成代码及效果
<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进行接收并使用在子组件模板中。
子组件对父组件的传值
咱们继续使用上面实现点击递加的例子 :


所以我们将父组件中的数据复制到一个变量中(做个替身),然后我们操作子组件中这个变量即可。
最终形式如下:
<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: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>
