一,什么是組件:
1, 擴(kuò)展HTML元素,封裝可重用的代碼
如下圖,左側(cè)是一個頁面 被拆分成小的區(qū)塊,每個區(qū)塊對應(yīng)一個組件,組件可以嵌套,最終組合成完成頁面

2, 組件設(shè)計原則
頁面上每個獨立的可視/可交互區(qū)域視為一個組件
每個組件對應(yīng)一個工程目錄,組件所需要的各種資源在這個目錄下就近維護(hù)
頁面不過是組件的容器,組件可以嵌套自由組合形成完整的頁面
如下示例:把組件拆分成根組件和兩個子組件:
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, 組件還具備復(fù)用性
如下示例:有三個組件,其中一個組件發(fā)生變化不會影響到其他組件,里面的數(shù)據(jù)屬于組件獨享
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 聲明,聲明后 可在模板內(nèi)使用,性能較高,使用起來麻煩,局部組件定義盡量用駝峰式命名,使用時,要做一個名字和組件間的映射;
三,組件間傳值及通信
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');
上面代碼的意思是 父組件調(diào)用子組件的標(biāo)簽 通過標(biāo)簽上的屬性向子組件傳遞值,子組件通過使用props來接收content屬性的內(nèi)容,接收定義好后,可直接在模板里使用;
當(dāng)要向子組件傳遞很多參數(shù)的時候,可使用下面方法:
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,單向數(shù)據(jù)流的概念:
子組件可以使用父組件傳遞過來的數(shù)據(jù),但是絕對不能直接修改父組件傳遞過來的數(shù)據(jù)(原因是會造成數(shù)據(jù)耦合,無法區(qū)分開),如需對父組件傳遞過來的子組件內(nèi)容進(jìn)行修改,可單獨在子組件里定義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可以輕松實現(xiàn)跨級訪問祖先組件的數(shù)據(jù)
示例如下:
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,父子組件通過事件進(jìn)行通信
由于子組件不能直接修改父組件,可在子組件調(diào)用 $emit()的方法,來實現(xiàn)點擊 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,展示在頁面中,當(dāng)點擊的時候,觸發(fā)了自身的事件 addCount,父組件通過接收此事件,執(zhí)行handleCountAdd方法,給count +=1;count變化會自動傳給子組件,子組件也會變化;
還可以通過事件傳參給父組件跟多參數(shù)
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');
父子組件通過事件進(jìn)行通信 還可以通過 v-model進(jìn)行代碼簡化,(只能綁定基本類型的數(shù)據(jù))
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標(biāo)簽插入子組件,子組件通過<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是不能直接綁定事件(父組件想往子組件傳遞一些節(jié)點或者元素標(biāo)簽,直接把元素寫在組件標(biāo)簽中間即可)
slot 使用數(shù)據(jù)作用域問題:
父模板里調(diào)用數(shù)據(jù)屬性,使用的都是父模板里的數(shù)據(jù);
子模板里調(diào)用的數(shù)據(jù)屬性,使用的都是子模板里的數(shù)據(jù);
如果未傳插槽的內(nèi)容,去調(diào)用插槽,沒有內(nèi)容,可用default value作為默認(rèn)值,示例如下:
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進(jìn)行拆分,分開調(diào)用,示例如下:
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,作用域插槽:
作用域插槽解決了當(dāng)子組件渲染的內(nèi)容由父組件決定的時候,可通過作用域插槽實現(xiàn),能夠讓父組件調(diào)用子組件的數(shù)據(jù)
作用域插槽執(zhí)行流程:
父組件調(diào)用名為list的子組件,在子組件循環(huán)內(nèi)容時,調(diào)用slot時,把item數(shù)據(jù)傳給slot,父組件通過v-slot ="slotProps"數(shù)據(jù)對象接收子組件內(nèi)容,接收傳過來的內(nèi)容后,通過{{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');
四,動態(tài)組件和異步組件:
示例如下:
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');
以上寫法代碼量稍大,可通過動態(tài)組件的概念進(jìn)行代碼簡化:
以下是簡化后的代碼:
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>:具有緩存特性 當(dāng)動態(tài)組件第一次渲染的時候,會把組件狀態(tài),變更情況記錄下來;動態(tài)組件會結(jié)合<keep-alive>一起使用
異步組件:在大型項目中 ,我們可能需要將應(yīng)用分割成小一些的代碼塊,并且只在需要的時候才從服務(wù)器加載一個模塊,只有在這個組件需要被渲染的時候才會觸發(fā)該工廠函數(shù),且會把結(jié)果緩存起來供未來重渲染
示例如下:
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');
