1.組件定義
全局組件和單文件組件兩種方式
2.1 全局組件
源碼分析1:src\core\global-api\assets.js
//是component,filter,directive三個的綜合方法
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
//['component'陪汽,'filter','directive']循環(huán)遍歷這三個方法
ASSET_TYPES.forEach(type => {
//動態(tài)附加三個靜態(tài)方法展辞,然后分別去定義
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
//如果def是對象,那么definition就是組件的配置對象
if (type === 'component' && isPlainObject(definition)) {
//定義組件name
definition.name = definition.name || id
//extend創(chuàng)建組件構(gòu)造函數(shù),def變成了構(gòu)造函數(shù)
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
//注冊 this.options[components][comp]=Ctor
//在當(dāng)前vue的選項中加上components選項祖秒,這樣的話
//定義全局組件痕囱,將來會被繼承在所有的子組件里
this.options[type + 's'][id] = definition
return definition
}
}
})
}
再看看extend,js
1.2 單文件組件
其實寫的并不是一個組件而是組件的一個配置對象田轧。
vue-loader會編譯template為render函數(shù),最終導(dǎo)出的依然是組件配置對象鞍恢。
(style里面的代碼會用style-loader傻粘,css里的會用css-loader處理成css文件)
所以我們現(xiàn)在寫的.vue文件最終都會導(dǎo)出為js對象,這些對象都是組件的配置對象而不是組件的構(gòu)造函數(shù)帮掉。
2.組件化優(yōu)點(提高維護性弦悉,測試性,復(fù)用性)
源碼位置:lifecycle.js-mountComponent()
組件蟆炊、watcher稽莉、渲染函數(shù)和更新函數(shù)之間的關(guān)系
mountComponent()定義了組件和watcher一一對應(yīng)的關(guān)系。
//組件的實例在執(zhí)行$mount的時候會調(diào)用的方法
export function mountComponent (
//創(chuàng)建一個組件的時候也創(chuàng)建了一個與之對應(yīng)的watcher實例盅称,
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
...
updateComponent = () => {
vm._update(vm._render(), hydrating)
//調(diào)用組件的更新函數(shù)和渲染函數(shù)
}
創(chuàng)建一個組件的時候也創(chuàng)建了一個與之對應(yīng)的watcher實例肩祥,
如果這個組件中的數(shù)據(jù)發(fā)生了變化,其實只會調(diào)用該組件的渲染函數(shù)
如果在應(yīng)用程序中缩膝,合理切割組件的力度混狠,比如說將一個經(jīng)常發(fā)生數(shù)據(jù)變化的一塊內(nèi)容,切割成一個組件疾层,將來頻繁執(zhí)行的渲染函數(shù)和更新函數(shù)和打補丁的范圍就變得更小了.所以把數(shù)據(jù)變化頻繁的提取為組件将饺,可以有效地提升性能。
3.組件化的實現(xiàn)
構(gòu)造函數(shù)痛黎,src\core\global-api\extend.js
實例化及掛載予弧,src\core\vdom\patch.js-createElm()
組件的構(gòu)造函數(shù)通過extend來生成,extend執(zhí)行的時間點不完全相同湖饱,如果說是全局注冊組件掖蛤,是立刻執(zhí)行,甚至在當(dāng)前實例化之前就執(zhí)行了井厌。如果是一個局部組件蚓庭,聲明在一個單文件組件中致讥,可能在運行時的某個時刻才會去執(zhí)行extend方法,具體是什么時刻可以看一下patch的源碼器赞。
我們都知道dom是一個樹垢袱,那虛擬dom也是一個樹,那么這棵樹的起始點一定有一個根節(jié)點港柜,實際上根節(jié)點就是起始點请契,那么根節(jié)點一定不是一個組件,而是vue的根實例夏醉。那就不存在自定義組件這些東西爽锥。
當(dāng)vue實例化的時候,它執(zhí)行掛載之后授舟,會有一個初始化過程救恨,要執(zhí)行打補丁patch,在此過程中會執(zhí)行createElm方法释树,因為根節(jié)點需要創(chuàng)建相對應(yīng)的一個真實的dom,
看看createElm方法中有幾行代碼是和組件化息息相關(guān)的擎淤。
//如果要創(chuàng)建的是組件奢啥,走下面的流程,如果傳入的是vue實例,就不執(zhí)行
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
···javascript
//這里createComponent是把前面的那個執(zhí)行的結(jié)果vnode轉(zhuǎn)化為真實的dom
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
//獲取管理鉤子函數(shù)
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
//存在init鉤子嘴拢,則執(zhí)行之創(chuàng)建實例并掛載
//這個初始化函數(shù)是在create-component.js中定義的
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
//如果組件實例存在
if (isDef(vnode.componentInstance)) {
//屬性初始化
initComponent(vnode, insertedVnodeQueue)
//dom插入操作
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
> create-component.js
···javascript
//默認(rèn)組件管理鉤子
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
//不是keep-alive而是要重新創(chuàng)建實例的時候
//創(chuàng)建組件實例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
//子組件里面的子元素或者子組件也進行了初始化
//創(chuàng)建完成并掛載(掛載后才能從虛擬dom變成真實dom)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
···
實例化的過程是自上而下的桩盲,從根往葉子節(jié)點進行創(chuàng)建,但是由于子節(jié)點的創(chuàng)建會先執(zhí)行席吴,子節(jié)點創(chuàng)建后就立刻掛載了赌结,所以掛載是從下而上的。
# 4.總結(jié)
+ 基礎(chǔ)部分
1.組件是獨立和可復(fù)用的代碼組織單元孝冒。組件系統(tǒng)是Vue核心特性之一柬姚, 它使開發(fā)者使用小型、獨立和通匙校可
復(fù)用的組件構(gòu)建大型應(yīng)用;
2.組件化開發(fā)能大幅提高應(yīng)用開發(fā)效率量承、測試性、復(fù)用性等;
+ 落地
3.組件使用按分類有:
頁面組件:用路由來導(dǎo)航一些頁面組件穴店,復(fù)用性不那么強撕捍,但是是組織頁面直接來回切換的必備組件
業(yè)務(wù)組件:登錄組件,購物車?yán)锏幕照陆M件泣洞,有很強的業(yè)務(wù)性和通用性
通用組件:按鈕忧风,表單,輸入框等等;
+ 特點
4. vue的組件是基于配置的球凰,我們通常編寫的組件是組件配置而非組件,框架后續(xù)會生成其構(gòu)造函數(shù)狮腿,它們基于
VueComponent,這個類擴展于Vue该窗,擴展的過程中,會繼承于vue中已經(jīng)有的一些選項蚤霞。
5. vue中常見組件化技術(shù)有:屬性prop,自定義事件,插槽等,它們主要用于組件通信酗失、擴展等;
+ 注意事項
6.合理的劃分組件,有助于提升應(yīng)用性能;
切割出經(jīng)常發(fā)生數(shù)據(jù)變化的組件昧绣,將來watcher去更新的時候规肴,只會重新渲染它對應(yīng)的組件,而不會影響其他的地方夜畴。
7.組件應(yīng)該是高內(nèi)聚拖刃、低耦合的;
高內(nèi)聚:組件本來就是一個獨立功能單元,功能應(yīng)該是單一的贪绘,獨立的兑牡,這樣組件才更容易復(fù)用。
低耦合:不應(yīng)該與其他組件有太多的耦合關(guān)系税灌。
8.遵循單向數(shù)據(jù)流的原則均函。
通過屬性把值傳進來或者通過vuex的方式直接把值注入進來。如果要改變值菱涤,通過事件派發(fā)出去苞也,然后讓父級接管這個事件,從而修改數(shù)據(jù)粘秆。如果使用vuex如迟,使用commit或者dispatch的方式,讓vuex去改變這個值攻走。而不是組件自己去改數(shù)據(jù)殷勘。讓組件功能更單一。