Vue.js 章7:组件、使用props传递数据、父子组件通信
这一章感觉要学好久了...
<body>
<!-- 组件:Vue.js最核心、设计最精彩的功能,也是最难掌握的(555) -->
<!-- 组件:提高重用性,让代码能够复用 -->
<div id="app1">
<my-component-01></my-component-01>
<my-component></my-component>
</div>
<!-- 在某些情况下,组件的模板会受到html的限制,如table标签内规定只允许是tr、td、th这些表格元素,
所以直接在table标签内使用组件是无效的,这种情况下可以使用特殊的is属性来挂载组件 -->
<div id="app2">
<table>
<tbody is = 'my-component-table'>
</tbody>
</table>
</div>
<div id="app3">
<my-component-02></my-component-02>
</div>
<div id="app4">
<my-component-03></my-component-03>
<my-component-03></my-component-03>
<my-component-03></my-component-03>
<my-component-03></my-component-03>
<my-component-03></my-component-03>
</div>
</body>
<script>
// 组件需要注册之后才能使用,有全局注册(任何Vue实例都可以使用)以及局部注册两种方式
// 全局注册,注册的组件需要在初始化根实例之前注册了组件;
// 局部注册,通过使用组件实例选项注册,可以使组件仅在另一个组件或者实例的作用域中可用:
Vue.component('my-component-01',{
template:'<div>这是第一个组件的内容</div>'
//组件内容
});
Vue.component("my-component-table",{
template:'<div>这是塞进表格组件里的内容</div>'//渲染时会将tbody替换
});
var childCompo = {
template:'<div>这是第一个局部注册组件的内容</div>'
};
var app1 = new Vue({
el:"#app1",
components:{
'my-component':childCompo
}
});
var app2 = new Vue({
el:"#app2"
});//注意了,使用组件时要确保已经将元素挂载到Vue实例
//组件还可以像vue实例那样使用其他选项,比如data、computed、methods等,但在使用data时有区别:
//data必须是函数,将数据return出去
Vue.component('my-component-02',{
template:'<div>{{ message }}</div>',
data() {
return {
message:'组件内容,这里的data是函数'
}
},
});
var app3 = new Vue({
el:"#app3"
});
//js的对象是引用关系(值传递和引用传递的相关知识),如果return的对象引用了外部的一个对象,那么这个对象就是共享的
//任何一方修改都会同步
var dataObj = {
counter:0
};
Vue.component('my-component-03',{
template:'<button v-on:click="counter++">记录点击次数:{{ counter }}</button>',
// data:function(){
// return {
// dataObj//上面那个对象
// }
// }会报错的写法
data:function(){
return dataObj
},
// data:function(){
// return {
// counter:0
// }
// }
})
var app4 = new Vue({
el:"#app4",
});
//如果这样,每点击一个按钮,所有按钮的计数次数都会+1,改写为96~100行的形式
//原理:给每个组件返回一个新的data对象
</script>
重难点是后面两个部分,使用props传递数据、父子组件通信
<body>
<div id="app1">
<my-component-01 message-from="来自父组件的数据"></my-component-01>
</div>
<div id="app2">
<input type="text" v-model="parentMessage">
<my-component-02 :message = "parentMessage"></my-component-02>
</div>
<div id="app3">
<my-component-03 message="[1,2,3]"></my-component-03>
<my-component-03 :message="[1,2,3]"></my-component-03>
<!-- 直接传递数字、布尔值、数组、对象而不使用v-bind那么传递过去的只会是字符串!例如上面两个组件中的message.length,一个是7,一个是3 -->
</div>
<div id="app4">
<my-component-04 :init-count="1"></my-component-04>
<my-component-04 :width="200"></my-component-04>
</div>
<div id="app5">
<input type="text" v-model="parentMessage">
<my-component-05 :prop_a = "parentMessage"></my-component-05>
</div>
<div id="app6">
<p>总计:{{ total }}</p>
<my-component-06
@increase = "handleGetTotal"
@reduce = "handleGetTotal"></my-component-06>
</my-component-06>
</div>
<div id="app7">
<p>app7:{{ total }}</p>
<my-component-07 v-model = "total"></my-component-07>
</div>
<div id="app8">
<p>app8:{{ total }}</p>
<my-component-08 v-model="total"></my-component-08>
<button @click = "handleReduce">-1</button>
</div>
</body>
<script>
Vue.component('my-component-01',{
//使用props声明需要从父级接收的数据
props:['messageFrom'],//如果要传递多个数据,直接在props数组中添加项即可
//注意短横杠分隔命名与驼峰式命名
template:'<div>{{ messageFrom }}</div>',
});
var app1 = new Vue({
el:"#app1"
});
//上面是写死的数据,但大多数情况传递的数据是来自父级的动态数据,此时应使用v-bind动态绑定
Vue.component('my-component-02',{
props:['message'],
template:'<div>子组件:{{ message }}</div>'
});
var app2 = new Vue({
el:"#app2",
data:{
parentMessage:''
//数据传递:pM更改-->v-model更改-->message值更改(:message = "parentMessage")-->props中的message-->子组件中的message
}
});
Vue.component('my-component-03',{
props:['message'],
template:'<div>{{ message.length }}</div>',
data:function(){
console.log(this,this.message);
return {messages:this.message};
}
});
var app3 = new Vue({
el:"#app3",
});
//业务中经常会遇到的两种改变prop的情况:
//1.父组件传递初始值,子组件将其保存起来,在自己的作用域下任意传值与修改,
//2.另一种情况:prop作为需要进行转变的初始值传入
Vue.component('my-component-04',{
props:['initCount','width'],
template:'<div>\
<p>{{ count }}</p>\
<div :style="style"></div>\
</div>',
data:function(){
return {
count:this.initCount,//在组件初始化时会获取来自父组件的initCount,之后就与之无关了
}//返回的必须是一个数据对象哈小老弟
},
computed: {
style:function(){
return {
width:this.width + 'px'
//这里会得到:style="width:200px"
}
}
},
});
var app4 = new Vue({
el:"#app4",
});
//数据验证,当某个数据不符合输入类型时会在控制台弹出警告
Vue.component('my-component-05',{
props:{
//数字类型限定
prop_a:Number,
// propA:[Number,String],
//布尔值限定,默认为true,必须传入
propB:{
type:Boolean,
default:true,
// required:true
},
//默认值必须是一个函数来返回
propC:{
default:function(){
return [];
}
},
//自定义验证函数
propD:{
validator:function(value){
return value > 500;
}
}
},
template:'<div>子组件只能是数字:{{ prop_a }}</div>',
})
var app5 = new Vue({
el:"#app5",
data:{
parentMessage:0
}
});
//当子组件需要向父组件传递数据时需要用到自定义事件以及$emit()方法
Vue.component('my-component-06',{
template:'\
<div>\
<button @click="handleIncrease()">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data:function(){
return {
counter:0
}
},
methods: {
handleIncrease:function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce:function(){
this.counter--;
this.$emit('reduce',this.counter);
}
},
});
//分析一下数据的传递:
//1,total最初为0
//点击+1键:触发组件06methods中的handleIncrease方法,counter++,通过$emit()方法(第一个参数是自定义,第二个是可不填或多填的参数)
//父组件上的v-on监听到increase事件,触发handleGetTotal()方法,同时$emit()方法的第二个参数被传入handleGetTotal()
//this.total=totalNum,即新的counter值
//v-on还可以用于监听原生的dom事件,但需要加上.native修饰符
//eg:<m-c @click.native="handleClick"></m-c>
var app6 = new Vue({
el:"#app6",
data:{
total:0
},
methods:{
handleGetTotal:function(totalNum){
this.total = totalNum;
}
}
});
Vue.component('my-component-07',{
template:'\
<button @click = "handleClick">+1</button>',
data:
function(){
return {
counter:0
}
},
methods: {
handleClick:function(){
this.counter++;
this.$emit('input',this.counter);
}
},
});
// 这一块的数据流动有点奇怪,我尽力分析一下:
// 点击按钮 触发handleClick事件---counter++,并将事件名称input与当前counter值传递给父组件,
// 注意,在前面几章有一个知识点,v-model实际上是一个语法糖,从前面复制过来并加上些详细点的解释:
// v-model你可以理解成是value的更高级,:value(v-bind)属于数据单向绑定(从script到html的单向),v-model属于双向绑定
// v-model官方给出的说法是:这其实是一个简写的形式,v-model实际执行的是下面的绑定:
// <input type="text" v-bind:value="dataA" v-on:input="dataA = $event.target.value" />
// (会根据在不同的表单控件上执行不同的作用,如option与button)
// 在本例(app7)中则是:<input type="text" v-bind:value="total" v-on:input ="total = $event.target.value" />
//然后更新total值后就会影响视图如{{ total }}辣~
// 接前面对于数据流动的分析:子组件将input事件传递给父组件后,相当于组件07上触发了
//input事件,使得v-model="total"获得更新,继而更新{{ total }}
//前面碰到的例子都是这样写的:<input v-model="a">
// <p>{{ a }}</p>
var app7 = new Vue({
el:"#app7",
data:{
total:0
}
});
Vue.component('my-component-08',{
props:['value'],
template:'<input :value = "value" @input = "updateValue">',
methods:{
updateValue:function(event){
this.$emit('input',event.target.value);
}
}
});
var app8 = new Vue({
el:"#app8",
data:{
total:0
},
methods:{
handleReduce:function(){
this.total--;
}
}
});
//这一部分的数据变化方向:
//1:点击-1按钮,执行handleReduce方法,total--,直接反馈到视图中的{{ total }}与v-model="total"(双向绑定)
//2.输入值,执行updateValue方法,同时更新value(记得前面说的v-model是个语法糖以及实际展现)
//value被更新,子组件获取的是实时的value,也会获得更新
</script>
一个需要注意点的地方:
app5、组件05部分:
如果规定了类型,那么一开始传入的值就必须是符合类型要求的,否则会报错