前言
在Vue
中,萬(wàn)物皆組件渡紫,學(xué)習(xí)組件乃是Vue
的根基所在到推,假如一個(gè)頁(yè)面相當(dāng)于一張圖,那么組件就是一個(gè)小小的拼圖惕澎,通過(guò)組件可以拼出各式各樣的圖莉测,這也是Vue
的魅力所在。
正文
基本示例
Vue.component('button-counter',{
data() {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
上面我們創(chuàng)建了一個(gè)全局的名字為button-couter
的組件唧喉,當(dāng)我們要在根實(shí)例(app.vue
)中使用它的時(shí)候,直接把這個(gè)組件作為自定義的標(biāo)簽來(lái)使用捣卤。
<div>
<button-counter></button-counter>
</div>
局部注冊(cè)
上面演示了如何全局注冊(cè)一個(gè)組件,但是組件不能全部都全局注冊(cè)八孝,會(huì)造成代碼的冗余董朝,所以大部分的組件需要局部注冊(cè)。
如何局部注冊(cè)呢干跛,首先把一個(gè)組件作為一個(gè)單個(gè)的文件放在components
文件夾下子姜,在文件中將組件export
,然后在實(shí)例中import
它就實(shí)現(xiàn)了組件的局部注冊(cè)楼入。
// 組件文件的基本格式
// components/buttonCounter.vue
<template>
// 模板內(nèi)容
</template>
<script>
export default {
name: '', //這個(gè)name對(duì)外沒(méi)有實(shí)際作用哥捕,但是可以讓該組件遞歸調(diào)用自己
data() {
return {}
}
}
</script>
<style scoped>
// scoped標(biāo)明style里面樣式僅適用于當(dāng)前組件
</style>
下面我們?cè)趯?shí)例中調(diào)用它
import buttonCounter from './component/buttonCounter'
// 引入組件,組件的名字也可以隨意定義(假如定一個(gè)abc)浅辙,下面使用它的時(shí)候就用<abc></abc>
<buttonCounter></buttonCounter>
基礎(chǔ)組件的自動(dòng)全局化注冊(cè)
有一些組件特別小扭弧,但是復(fù)用率特別高,必須輸入框按鈕之類的记舆,這些組件就必須全局注冊(cè)鸽捻,但是組件又特別多,一個(gè)個(gè)注冊(cè)顯得代碼特別臃腫,所以下面就用一段代碼來(lái)實(shí)現(xiàn)自動(dòng)注冊(cè)御蒲。
假如我們把基礎(chǔ)組件放在components
文件夾下衣赶,組件的文件名都以Base開(kāi)頭
,比如BaseInput
,BaseButton
厚满。首先我們引入符合Base
開(kāi)頭匹配規(guī)則的所有組件府瞄,這里要用到require.context
方法,然后用文件的名字來(lái)注冊(cè)全局組件碘箍。
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
'./components', //根目錄
false, // 是否查詢子目錄
/Base[A-Z]\w+\.(vue|js)$/ //匹配規(guī)則
)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
Vue.component(
componentName,
componentConfig.default || componentConfig
// 組件中 export default 和 export
)
})
上面代碼中upperFirst
和camelCase
都是lodash庫(kù)里面的方法遵馆。
組件里面也是有
computed
,methods
,watch
和生命周期鉤子
的,唯獨(dú)沒(méi)有el
丰榴,這是根實(shí)例獨(dú)有的
那么data
為何是個(gè)函數(shù)而不是對(duì)象呢货邓,這是因?yàn)榻M件是可以復(fù)用的,每次使用就創(chuàng)建一個(gè)新的實(shí)例四濒,把data
里面的屬性作為函數(shù)的返回值的話换况,它就實(shí)現(xiàn)了每個(gè)實(shí)例的屬性的私有化,跟閉包一樣原理盗蟆。
通過(guò)prop
向組件傳遞數(shù)據(jù)
Vue.component('blog-post',{
props: ['title','name'],
template: '<h3>{{ name }}{{ title }}</h3>'
})
我們創(chuàng)建了一個(gè)blog-post
組件戈二,并用prop
接收了2個(gè)參數(shù)title
和name
,下面在根實(shí)例中往組件中注入值
<blog-post title="myTitlt" name="myName"></blog-post>
上面是傳了幾個(gè)單個(gè)值,假如傳的值比較多喳资,或者傳的值是個(gè)數(shù)組的時(shí)候就要換一個(gè)方式
Vue.component('blog-post',{
props: ['post'],
template: '<h3>{{ post.name }}{{ post.title }}</h3>'
})
根實(shí)例中傳值
<blog-post v-for="post in posts" :post="post"></blog-post>
當(dāng)父級(jí)傳給子組件值時(shí)候觉吭,子組件改變這個(gè)值也會(huì)同時(shí)更改父級(jí)的值,這樣就會(huì)發(fā)生混亂仆邓,所以當(dāng)子組件有改變值的情況下亏栈,最好將父級(jí)傳來(lái)的值賦給自己的屬性
props: ['name'],
data() {
return {
myName: this.name
}
}
組件向父級(jí)傳值
這里要用到一個(gè)Vue
的內(nèi)置方法$emit
,這個(gè)函數(shù)就相當(dāng)于js
中的trigger()
,就是在組件中可以觸發(fā)實(shí)例中綁定的事件宏赘,而且這個(gè)方法可以傳遞參數(shù)绒北。
Vue.component('welcome-button', {
template: `
<button v-on:click="$emit('welcome',1)">
Click me to be welcomed
</button>
`
})
組件中觸發(fā)welcome
事件并傳了個(gè)參數(shù)1,那么在實(shí)例中綁定這個(gè)事件
<welcome-button @welcome="couter=$event"></welcome-button>
<welcome-button @welcome="sayHi"></welcome-button>
new Vue({
data: {
couter: 0
},
methods: {
sayHi(argsCouter) {
// ..
}
}
})
在實(shí)例中可以使用$event
獲取$emit
傳遞的參數(shù)察署,如果$emit
觸發(fā)的事件引用的是另外一個(gè)事件,那么$emit
的參數(shù)將傳給它引用的事件(argsCouter
即$emit
傳遞的參數(shù))
在組件中使用v-model
<input v-model="searchText">
這是一個(gè)v-model
的使用場(chǎng)景闷游,那么它的本質(zhì)其實(shí)是
<input :value="searchText" @input="searchText=$event.target.value">
那么在組件上使用v-model
就相當(dāng)于
<custom-input :value="searchText" @input="$event"></custom-input>
等同于
<custom-input v-model="searchText"></custom-input>
為了讓它工作,在組件中首先得把value
綁在prop
上贴汪,然后它得觸發(fā)input
事件
Vue.component('custom-input',{
prps: ['value'],
template: `<input :value="value" @input="$emit('input',$event.target.value)"`
})
插槽
有時(shí)候我們?cè)谡{(diào)用組件的時(shí)候脐往,組件元素中間還有一些其他的東西,一般情況下扳埂,如果不處理這些東西會(huì)被舍棄的业簿,但是如果加上<slot>
元素,這些東西就會(huì)被渲染在組件之中
<custom-link>
這是一個(gè)鏈接
</custom-link>
那么在組件中阳懂,如果要使用這是一個(gè)鏈接
,就需要加<slot>
元素
<a href=''>
<slot></slot> //這是一個(gè)鏈接
</a>
組件元素的閉合標(biāo)簽中間內(nèi)容就是插槽
梅尤,在插槽可以加html
模板柜思,甚至可以加組件
具名插槽
上面的只是一個(gè)插槽,那它默認(rèn)渲染的就是組件中的<slot>
巷燥,但是當(dāng)有多個(gè)插槽赡盘,并且插槽還有指定位置的時(shí)候,就必須要給<slot>
加上name
屬性
<div class="container">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
那么在父級(jí)提供插槽內(nèi)容的時(shí)候缰揪,必須加一個(gè)<template>
元素包裹
<base-layout>
<template slot="header">
// 這里的內(nèi)容渲染header
</template>
// 這里的內(nèi)容沒(méi)有命名陨享,所以渲染<slot></slot>
<template slot="footer">
// 這里的內(nèi)容渲染footer
</template>
</base-layout>
卡槽也可以有默認(rèn)內(nèi)容的,
<slot>默認(rèn)內(nèi)容</slot>
钝腺,當(dāng)父級(jí)有傳給子組件內(nèi)容時(shí)抛姑,會(huì)覆蓋默認(rèn)內(nèi)容
動(dòng)態(tài)組件
前端開(kāi)發(fā)中經(jīng)常會(huì)出現(xiàn)選項(xiàng)卡之類的功能,實(shí)現(xiàn)它就需要不同的組件切來(lái)切去艳狐,這個(gè)功能可以通過(guò)Vue
的<component>
元素加一個(gè)特殊屬性is
來(lái)實(shí)現(xiàn)
<component :is="currentTab"></component>
當(dāng)點(diǎn)擊選項(xiàng)卡的nav
時(shí)途戒,只需要把currentTab
指向?qū)⒁袚Q的組件名即可
但是這樣實(shí)現(xiàn)有個(gè)問(wèn)題,就是每次切換都會(huì)創(chuàng)建一個(gè)組件的實(shí)例僵驰,我們更多其實(shí)是希望它第一次創(chuàng)建以后緩存下來(lái),那么就需要
keep-alive
元素
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
異步組件
有時(shí)候我們加載組件的時(shí)候并不希望它一開(kāi)始就加載進(jìn)來(lái)唁毒,而是調(diào)用它的時(shí)候再加載進(jìn)來(lái)蒜茴,例如在VueRouter
里面,當(dāng)我們跳轉(zhuǎn)到路由后再去加載它對(duì)應(yīng)的組件浆西。
components: {
'my-component': () => import('./my-component')
}
// 或者
component(resolve){
require(['@/index.vue'],resolve)
}
使用異步組件的時(shí)候粉私,返回的都是一個(gè)
promise
函數(shù)
結(jié)束語(yǔ)
組件相關(guān)的東西還有很多很多,比如官網(wǎng)的處理邊界情況,只不過(guò)應(yīng)用場(chǎng)景不是很多近零,所以暫時(shí)不深入研究诺核。
就這樣,收工久信。窖杀。。