组件可以分为局部组件和全局组件,定义方式如下:
// 局部组件定义方式
const Conter = {
}
const app = Vue.createApp({
components:{conter:Conter} //通过声明components来引用conter局部组件
});
// 全局组件定义方式
app.component('hello-world',{});
全局组件在定义组件名称的时候,采用小写字母,如果涉及多个单词,使用-来连接;
局部组件在定义名称的时候,采用首字母大写的方式,如果涉及多个单次,采用驼峰式命名。
在引用局部组件的时候,可以直接使用原名称,也可直接指定名称,如下:
components:{Counter,HelloWorld}
// 在使用的时候Vue会自动检测名称,并将Counter首字母改为小写,最终为counter
// 当时驼峰命名的时候,Vue也会自动将字母转为小写,多字母之间用中划线连接
template:`<div><conter/><hello-world/></div>`
v-bind来动态绑定属性传值,传入的值可动态改变,子组件接收的数据类型与传入的数据类型相同子组件在接收值的时候可以做数据类型判断、限定等操作。如下:
// 直接接收数据
app.component('counter',{
props:['content']
})
// 加入类型判断
app.component('counter',{
props:{
counter:{
type:Number, // 指定类型是数值类型
required:true, // 是否必须传入
default:123, // 当不满足需求的时候,采用此默认值
validator:function(value){ // value表示传入的数据
// 参数校验逻辑,最终返回一个boolean类型的结果
}
}
}
})
type可取的类型有:String、Boolean、Array、Object、Function、Symbol
在传多个数据的时候,可以使用以下两种方式:
data(){
return {
params:{
a:"1",
b:"2",
c:"3",
}
}
},
template:`<div><counter v-bind="params"/></div>`
// 此时,Vue会自动将params内a、b、c赋值到props中对应的属性
app.component('counter',{
props:['a','b','c'],
template:`<div>{{a}}-{{b}}-{{c}}</div>`
})
data(){
return {
params:{
a:"1",
b:"2",
c:"3",
}
}
},
template:`<div ><counter :params="params"/></div>`
app.component('counter',{
props:['params'],
template:`<div>{{params.a}}-{{params.b}}-{{params.c}}</div>`
})
由于html不支持驼峰规则,所有在定义标签属性的时候,涉及多个单词,需要用-连接,此时传值就存在一个问题,需要规避,如下:
data(){
return {
params:1234
}
},
template:`<div ><counter :counter-params="params"/></div>` //定义采用短横线连接的方式,不支持驼峰规则
app.component('counter',{
props:['counter-params'],
template:`<div>{{counter-params}}</div>`
})
// 如果采用上面的方式来接收传入的值就会出现counter-params是NaN
// 规避方法:将接收值改成驼峰规则。如下:
app.component('counter',{
props:['counterParams'],
template:`<div>{{counterParams}}</div>`
})
概念:子组件可以使用父组件传递的数据,但是不能修改传递过来的数据。
如果子组件的确需要修改传过来的数据,这个时候可以将传过来的数据复制到新的变量内,通过修改新的变量来实现。如下:
data(){
return {
param:1234
}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
props:['param'],
data(){
return{
myCount:this.param // 复制数据
}
}
template:`<div @click="myCount += 1">{{myCount}}</div>`
})
在父组件给子组件传值的时候,如果子组件没有通过props接收时,Vue会自动将传入的属性和值添加到子组件template的顶层dom对象中。如下:
data(){
return {
param:1234
}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
template:`<div>Counter</div>`
})
// 浏览器渲染后的效果如下:
<div param="1234">Counter</div>
如果此时不想Vue自动给顶层dom添加属性,可以用inheritAttrs:false来关闭此特性。如下:
data(){
return {
param:1234
}
},
template:`<div ><counter :param="param"/></div>`
app.component('counter',{
inheritAttrs:false,
template:`<div>Counter</div>`
})
如果子组件存在多个并列的顶层dom对象,Vue是无法自动实现添加属性的机制,可以手动的添加。如下:
data(){
return {
param:1234
}
},
template:`<div ><counter :param="param" message="111"/></div>`
app.component('counter',{
template:`
<div :param="$attrs.param">Counter1</div> //param属性
<div :message="$attrs.message">Counter2</div> //message属性
<div v-bind="$attrs">Counter2</div>` // 所有属性
})
不仅如此,还可以通过生命周期函数来获取属性和属性值,如下:
app.component('counter',{
mounted(){
console.log(this.$attrs.param)
},
template:`
<div :param="$attrs.param">Counter1</div> //param属性
<div :message="$attrs.message">Counter2</div> //message属性
<div v-bind="$attrs">Counter2</div>` // 所有属性
})
正常的数据流是父组件将数据传给子组件,而子组件是没有修改父组件属性值的权限,只能将属性值复制到子组件内进行操作,但是可以通过事件的方式将子组件改变的数据传给父组件,由父组件进行修改操作。如下:
const app = Vue.createApp({
data(){
return {
count:123
}
},
methods:{
addOneHandle(num){ // num是接收传递来的参数,可多个,与传入对应即可
this.count = this.count + num;
}
}
template:`<counter :count="count" @add-one="addOneHandle"/>`
});
app.component("counter",{
props:['count'],
methods:{
addOneHandle(){
this.$emit('addOne',2);// 调用父节点的add-one事件,传入一个参数2
}
},
template:`<div @click="addOneHandle">{{count}}</div>`
})
注意点:
this.$emit('addOne',2)的时候,调用事件的名称采用驼峰命名规则,@add-one父标签中命名使用中划线连接,因为html不支持驼峰规则规则命名emits属性来统一管理记录调用父组件的事件集合,如下:app.component("counter",{
props:['count'],
emits:['addOne'] // 这里既可以使用数组的方式,也可使用对象的方式,对象的方式可以引入函数做逻辑判断
methods:{
addOneHandle(){
this.$emit('addOne',2);// 调用父节点的add-one事件,传入一个参数2
}
},
template:`<div @click="addOneHandle">{{count}}</div>`
})
在同一个组件里面,数据可以通过v-model进行双向绑定,在父子组件之间也能通过这种方式实现双向绑定。如下:
const app = Vue.createApp({
data(){
return {
count:123,
other:234
}
},
template:`<counter v-model:count="count" v-model:other="other"/>`
});
app.component("counter",{
props:['count','other'],
methods:{
addOneHandle(){
this.$emit('update:count',this.count + 1);
this.$emit('update:other',this.other + 1);
}
},
template:`<div @click="addOneHandle">{{count}}-{{other}}</div>`
})
上面这种方式可以适用于多个参数,即多个v-model:xxx="xxx",如果是单个参数,可采用下面的方式简化:(不建议)
const app = Vue.createApp({
data(){
return {
count:123
}
},
template:`<counter v-model="count" />`
});
app.component("counter",{
props:['modelValue'],
methods:{
addOneHandle(){
this.$emit('update:modelValue',this.modelValue + 1);
}
},
template:`<div @click="addOneHandle">{{modelValue}}</div>`
})
const app = Vue.createApp({
template:`<counter>
<div>dom</div>
</counter>`
});
app.component("counter",{
template:`<div>
<slot></slot> // 通过slot标签,将父组件counter内的所有dom引入到此位置
</div>`
})
const app = Vue.createApp({
template:`<counter>
<template v-slot:header>
<div>header</div>
</template>
<template v-slot:footer> // 使用template标签作为占位符,这里的v-slot:footer可简化为#footer
<div>footer</div>
</template>
</counter>`
});
app.component("counter",{
template:`<div>
<slot name="header"></slot> //将header的标签放到body前
<div>
body content
</div>
<slot name="footer"></slot> //将footer的标签放到body后
</div>`
})
const app = Vue.createApp({
template:`<counter v-slot="slotProps"> // 接收子组件绑定的所有属性数据
<div>{{slotProps.index}}-{{slotProps.item}}</div>// 渲染接收到的数据
</counter>`
});
app.component("counter",{
data(){
return {
list:[1,2,3],
}
},
template:`<div>
<slot v-for="(item,index) in list" :item="item" :index="index"/> //将遍历出来的数据绑定到slot上
</div>`
})
通component标签及其固定属性is来控制子组件的加载情况,实现动态组件的效果,代码如下:
const app = Vue.createApp({
data(){
return {
showItem:"one-item"
}
},
methods:{
changeItem(){ // 通过点击事件,修改需要展示的组件
this.showItem = this.showItem === "one-item" ? "two-item": "one-item";
}
},
template: `
<component :is="showItem"/> //通过is属性,切换显示one或者two组件
<button @click="changeItem">切换</button>
`
})
app.component("one-item",{
template:`<div>this is one-item</div>`
})
app.component("two-item",{
template:`<div>this is two-item</div>`
})
app.mount("#root");
组件异步显示,详细见示例代码:
const app = Vue.createApp({
template: `
<div>this is static div</div>
<async-item /> // 加载子组件
`
})
app.component("async-item", Vue.defineAsyncComponent(() => {
return new Promise((resolve,reject) => {
setTimeout(()=>{
resolve({
template:`<div>this is async template</div>`
})
},4000); // 4秒后加载
})
}))
app.mount("#root");