一,什么是组件:
1, 扩展HTML元素,封装可重用的代码
如下图,左侧是一个页面 被拆分成小的区块,每个区块对应一个组件,组件可以嵌套,最终组合成完成页面
2, 组件设计原则
页面上每个独立的可视/可交互区域视为一个组件
每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护
页面不过是组件的容器,组件可以嵌套自由组合形成完整的页面
如下示例:把组件拆分成根组件和两个子组件:
const app = Vue.createApp({
template:`<div>
<moduleone />
<moduletwo />
</div>`
})
app.component('moduleone',{
template:`<div>moduleone</div>`
});
app.component('moduletwo',{
template:`<div>moduletwo</div>`
});
const vm = app.mount('#contentMain');
3, 组件还具备复用性
如下示例:有三个组件,其中一个组件发生变化不会影响到其他组件,里面的数据属于组件独享
const app = Vue.createApp({
template: `<div>
<modulecounter />
<modulecounter />
<modulecounter />
</div>`
})
app.component('modulecounter', {
data() {
return {
count: 1
}
},
template: `<div @click ="count += 1">{{count}}</div>`
});
const vm = app.mount('#contentMain');
二,局部组件和全局组件
1,全局组件
由于app.component 定义的组件为全局组件,父组件及其他子组件都可使用
示例如下:
const app = Vue.createApp({
template: `<div>
<modulecounter-parent />
<modulecounter />
</div>`
})
app.component('modulecounter-parent', {
template: `<modulecounter />`
});
app.component('modulecounter', {
data() {
return {
count: 1
}
},
template: `<div @click ="count += 1">{{count}}</div>`
});
const vm = app.mount('#contentMain');
全局组件,定义好后,即使不用 也是一直挂载在 vue实例上的,对性能有一定影响,但是可随时使用;
2,局部组件
局部组件定义方式如下示例:
const CounterChild = {
data() {
return {
count: 1
}
},
template: `<div @click ="count += 1">{{count}}</div>`
}
const app = Vue.createApp({
components: {
'counter-child': CounterChild
},
template: `<div>
<counter-child />
</div>`
})
const vm = app.mount('#contentMain');
局部组件 定义一个常量,通过 components 声明,声明后 可在模板内使用,性能较高,使用起来麻烦,局部组件定义尽量用驼峰式命名,使用时,要做一个名字和组件间的映射;
三,组件间传值及通信
1,组件间传值:
示例如下:
const app = Vue.createApp({
data(){
return{
message:'hello world'
}
},
template: `<div>
<moduletest :content ="message"/>
</div>`
});
app.component('moduletest', {
props:['content'],
template: `<div>{{content}}</div>`
});
const vm = app.mount('#contentMain');
上面代码的意思是 父组件调用子组件的标签 通过标签上的属性向子组件传递值,子组件通过使用props来接收content属性的内容,接收定义好后,可直接在模板里使用;
当要向子组件传递很多参数的时候,可使用下面方法:
const app = Vue.createApp({
data(){
return{
params:{
message:'hello world',
msgone:'java',
msgtwo:'javascript'
}
}
},
template: `<div>
<moduletest v-bind ="params"/>
</div>`
});
app.component('moduletest', {
props:['message','msgone','msgtwo'],
template: `<div>{{message}}--{{msgone}}--{{msgtwo}}</div>`
});
const vm = app.mount('#contentMain');
v-bind ="params" 等价于 :contnet ="params.contnet"
2,单向数据流的概念:
子组件可以使用父组件传递过来的数据,但是绝对不能直接修改父组件传递过来的数据(原因是会造成数据耦合,无法区分开),如需对父组件传递过来的子组件内容进行修改,可单独在子组件里定义data,
示例如下:
const app = Vue.createApp({
data(){
return{
count:1
}
},
template: `<div>
<moduletest :count ="count"/>
</div>`
});
app.component('moduletest', {
props:['count'],
data(){
return{
countNum:this.count
}
},
template: `<div @click ="countNum += 2">{{countNum}}</div>`
});
const vm = app.mount('#contentMain');
3,provide/inject 多级组件传值
vue中可以让子组件访问父组件,孙组件想要访问祖先组件就比较麻烦,可以通过provide/inject可以轻松实现跨级访问祖先组件的数据
示例如下:
const app = Vue.createApp({
data(){
return{
count:1
}
},
provide:{
count:1
},
template: `<div>
<moduletest-child :count ="count"/>
</div>`
});
//子组件
app.component('moduletest-child', {
template: `<moduletest-child-child />`
});
//孙组件
app.component('moduletest-child-child', {
inject:['count'],
template: `<div>{{count}}</div>`
});
const vm = app.mount('#contentMain');
4,父子组件通过事件进行通信
由于子组件不能直接修改父组件,可在子组件调用 $emit()的方法,来实现点击 count加1的功能,示例如下:
const app = Vue.createApp({
data(){
return{
count:1
}
},
methods:{
handleCountAdd(){
this.count += 2;
}
},
template: `<div>
<moduletest-child :count ="count" @add-count ="handleCountAdd"/>
</div>`
});
app.component('moduletest-child', {
props:['count'],
methods:{
handleClickCount(){
this.$emit('addCount');
}
},
template: `<div @click ="handleCountClick">{{count}}</div>`
});
const vm = app.mount('#contentMain');
以上代码逻辑是:子组件接收父组件传递过来的count,展示在页面中,当点击的时候,触发了自身的事件 addCount,父组件通过接收此事件,执行handleCountAdd方法,给count +=1;count变化会自动传给子组件,子组件也会变化;
还可以通过事件传参给父组件跟多参数
const app = Vue.createApp({
data(){
return{
count:1
}
},
methods:{
handleaddClick(count){
this.count = count;
}
},
template:`<div>
<counter :count ="count" @add-count ="handleaddClick" />
</div>`
})
app.component('counter',{
props:['count'],
emits:['add'],
methods:{
handleCount(){
this.$emit('addCount',this.count + 3)
}
},
template:`<div @click ="handleCount">{{count}}</div>`
});
const vm = app.mount('#contentMain');
父子组件通过事件进行通信 还可以通过 v-model进行代码简化,(只能绑定基本类型的数据)
const app = Vue.createApp({
data(){
return{
count:1
}
},
template:`<div>
<counter v-model ="count" />
</div>`
})
app.component('counter',{
props:['modelValue'],
methods:{
handleCount(){
this.$emit('update:modelValue',this.modelValue + 3)
}
},
template:`<div @click ="handleCount">{{modelValue}}</div>`
});
const vm = app.mount('#contentMain');
update:modelValue 及 modelValue是固定写法 不可变
5,插槽 slot 和具名插槽:
示例如下,通过插槽 把dom标签插入子组件,子组件通过<slot></slot>来使用
const app = Vue.createApp({
template:`
<counter>
<button>点击</button>
</counter>
<counter>
<div>点击</div>
</counter>
`
})
app.component('counter',{
template:`<div>
<input />
<slot></slot>
</div>`
});
const vm = app.mount('#contentMain');
slot是不能直接绑定事件(父组件想往子组件传递一些节点或者元素标签,直接把元素写在组件标签中间即可)
slot 使用数据作用域问题:
父模板里调用数据属性,使用的都是父模板里的数据;
子模板里调用的数据属性,使用的都是子模板里的数据;
如果未传插槽的内容,去调用插槽,没有内容,可用default value作为默认值,示例如下:
const app = Vue.createApp({
template:`
<counter>
<button>点击</button>
</counter>
<counter>
<div>点击</div>
</counter>
<counter>
</counter>
`
})
app.component('counter',{
template:`<div>
<input />
<slot>default value</slot>
</div>`
});
const vm = app.mount('#contentMain');
还可吧slot进行拆分,分开调用,示例如下:
const app = Vue.createApp({
data() {
return {
text: '点我'
}
},
template: `
<layout>
<template #header>
<div>header</div>
</template>
<template #footer>
<div>footer</div>
</template>
</layout/>
`
})
app.component('layout', {
methods: {
handleFormClick() {
alert('hahahaha')
}
},
template: `<div>
<slot name ="header"></slot>
<div>content</div>
<slot name ="footer"></slot>
</div>`
});
const vm = app.mount('#contentMain');
具名插槽:可通过#号来简写 v-slot:header 可简写成 #header
6,作用域插槽:
作用域插槽解决了当子组件渲染的内容由父组件决定的时候,可通过作用域插槽实现,能够让父组件调用子组件的数据
作用域插槽执行流程:
父组件调用名为list的子组件,在子组件循环内容时,调用slot时,把item数据传给slot,父组件通过v-slot ="slotProps"数据对象接收子组件内容,接收传过来的内容后,通过{{slotProps.item}} 使用子组件item传过来的值
const app = Vue.createApp({
template: `
<list v-slot ="{item}">
<div>{{item}}</div>
</list>
`
})
app.component('list', {
data(){
return{
list:['1','2','3']
}
},
template: `<div>
<slot v-for ="item in list" :item ="item" />
</div>`
});
const vm = app.mount('#contentMain');
四,动态组件和异步组件:
示例如下:
const app = Vue.createApp({
data() {
return {
componentshow: 'commont-hello'
}
},
methods: {
handleBtnClick() {
this.componentshow == 'commont-hello' ? this.componentshow = 'commont-world' : this.componentshow = 'commont-hello';
}
},
template: `
<commont-hello v-show ="componentshow == 'commont-hello'"/>
<commont-world v-show ="componentshow == 'commont-world'"/>
<button @click ="handleBtnClick">点击切换</button>
`
})
app.component('commont-hello', {
template: `<div>hello</div>`
});
app.component('commont-world', {
template: `<div>world</div>`
});
const vm = app.mount('#contentMain');
以上写法代码量稍大,可通过动态组件的概念进行代码简化:
以下是简化后的代码:
const app = Vue.createApp({
data() {
return {
componentshow: 'commont-hello'
}
},
methods: {
handleBtnClick() {
this.componentshow == 'commont-hello' ? this.componentshow = 'commont-world' : this.componentshow = 'commont-hello';
}
},
template: `
<component :is ="componentshow"/>
<button @click ="handleBtnClick">点击切换</button>
`
})
app.component('commont-hello', {
template: `<div>hello</div>`
});
app.component('commont-world', {
template: `<div>world</div>`
});
const vm = app.mount('#contentMain');
如要需要缓存可使用<keep-alive>:具有缓存特性 当动态组件第一次渲染的时候,会把组件状态,变更情况记录下来;动态组件会结合<keep-alive>一起使用
异步组件:在大型项目中 ,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块,只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
示例如下:
const app = Vue.createApp({
template: `
<div>
<common-item />
<async-common-item />
</div>
`
})
app.component('common-item', {
template: `<div>hahahaha</div>`
});
app.component('async-common-item', Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div>this is async component</div>`
})
}, 4000)
})
}))
const vm = app.mount('#contentMain');