组件可以分为局部组件和全局组件,定义方式如下:
// 局部组件定义方式
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>`
父组件传值到子组件
- 静态传参:通过非动态绑定的属性进行传值,传入的值提前写入,传到子组件统一转为String类型
- 动态传参:通过
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>`
})
Non-props特性
在父组件给子组件传值的时候,如果子组件没有通过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>`
})
插槽分类讲解
- 基本插槽:通过在引用子组件的时候,传递dom对象给子组件的一种方式,如下:
const app = Vue.createApp({
template:`<counter>
<div>dom</div>
</counter>`
});
app.component("counter",{
template:`<div>
<slot></slot> // 通过slot标签,将父组件counter内的所有dom引入到此位置
</div>`
})
- 具名插槽:如果父组件传递多个dom,这些dom需要放在子组件中的不同位置,直接采用基本插槽的全部引用,就会存在问题,这里就引入了具名组件。
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");