Study Notes
本博主會持續(xù)更新各種前端的技術(shù)岔帽,如果各位道友喜歡骡苞,可以關(guān)注、收藏撤蟆、點贊下本博主的文章。
Vue.js 源碼剖析-組件化
組件化可以讓我們方便的把頁面拆分成多個可重用的組件
組件是獨立的允睹,系統(tǒng)內(nèi)可重用霸奕,組件之間可以嵌套
-
有了組件可以像搭積木一樣開發(fā)網(wǎng)頁
例如,你可能會有頁頭汛蝙、側(cè)邊欄烈涮、內(nèi)容區(qū)等組件,每個組件又包含了其它的像導(dǎo)航鏈接窖剑、博文之類的組件坚洽。
- 下面我們將從源碼的角度來分析 Vue 組件內(nèi)部如何工作
- 組件實例的創(chuàng)建過程是從上而下(先創(chuàng)建父組件再創(chuàng)建子組件)
- 組件實例的掛載過程是從下而上(先掛載子組件再掛載父組件)
組件定義
- 注冊 Vue.component()入口
// 注冊 Vue.directive()、 Vue.component()西土、Vue.filter()
initAssetRegisters(Vue);
export function initAssetRegisters(Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
// 遍歷 ASSET_TYPES 數(shù)組讶舰,為 Vue 定義相應(yīng)方法
// ASSET_TYPES 包括了directive、 component翠储、filter
ASSET_TYPES.forEach((type) => {
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') {
if (type === 'component' && config.isReservedTag(id)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' +
id,
);
}
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id;
// 把組件配置轉(zhuǎn)換為組件的構(gòu)造函數(shù)
definition = this.options._base.extend(definition);
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition };
}
// 全局注冊绘雁,存儲資源并賦值
// 例如:this.options['components']['comp'] = definition
// 當(dāng)前this指向vue實例
this.options[type + 's'][id] = definition;
return definition;
}
};
});
}
/* @flow */
import ...
export function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
* 每個實例構(gòu)造函數(shù)(包括Vue)都具有唯一的cid。
* 這使我們能夠為創(chuàng)建一個包裹的“子構(gòu)造函數(shù)”通過原型繼承并對其進行緩存援所。
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
// Vue構(gòu)造函數(shù)
const Super = this
const SuperId = Super.cid
// 從緩存中加載組件的構(gòu)造函數(shù)
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production') {
// 如果是開發(fā)環(huán)境驗證組件的名稱
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
}
// 組件對應(yīng)的構(gòu)造函數(shù)
const Sub = function VueComponent (options) {
// 調(diào)用 _init() 初始化
this._init(options)
}
// 原型繼承自 Vue
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
// 合并 options
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
// 對于props和計算屬性庐舟,我們在Vue實例上定義代理getter時擴展原型。
// 這樣可以避免為每個創(chuàng)建的實例調(diào)用Object.defineProperty住拭。
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
// 集成extension/mixin/plugin
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
// 集成directive挪略、 component、filter
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
// 把組件構(gòu)造函數(shù)保存到 Ctor.options.components.comp = Ctor
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
// 在擴展時保留對super 選項的引用滔岳。
// 稍后在實例化時杠娱,我們可以檢查Super的選項是否已更新。
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
// 把組件的構(gòu)造函數(shù)緩存到 options._Ctor
cachedCtors[SuperId] = Sub
return Sub
}
}
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}
組件創(chuàng)建和掛載
-
組件 VNode 的創(chuàng)建過程
- 創(chuàng)建根組件谱煤,首次 _render() 時摊求,會得到整棵樹的 VNode 結(jié)構(gòu)
- 整體流程:new Vue() --> $mount() --> vm._render() --> createElement() --> createComponent()
- 創(chuàng)建組件的 VNode,初始化組件的 hook 鉤子函數(shù)
src/core/vdom/create-element.js 中的
_createElement
方法調(diào)用了createComponent
方法創(chuàng)建組件的 VNode
...
if (typeof tag === 'string') {
let Ctor;
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
// 如果是瀏覽器的保留標(biāo)簽刘离,創(chuàng)建對應(yīng)的 VNode
if (config.isReservedTag(tag)) {
// platform built-in elements
// 創(chuàng)建vnode對象
vnode = new VNode(
config.parsePlatformTagName(tag),
data,
children,
undefined,
undefined,
context,
);
} else if (
isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
) {
// component
// 如果是自定義組件
// 查找自定義組件構(gòu)造函數(shù)的聲明
// 根據(jù)Ctor創(chuàng)建組件的VNode
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(tag, data, children, undefined, undefined, context);
}
} else {
// direct component options / constructor
// 如果tag不是字符串室叉,即代表其是一個組件
// 創(chuàng)建組件的VNode
vnode = createComponent(tag, data, context, children);
}
...
/* @flow */
import ...;
// hooks to be invoked on component VNodes during patch
// 鉤子函數(shù)定義的位置(init()鉤子中創(chuàng)建組件的實例)
const componentVNodeHooks = {
init(
vnode: VNodeWithData,
hydrating: boolean,
parentElm: ?Node,
refElm: ?Node,
): ?boolean {
if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
// 創(chuàng)建組件實例掛載到 vnode.componentInstance
const child = (vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance,
parentElm,
refElm,
));
// 調(diào)用組件對象的 $mount(),把組件掛載到頁面
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
} else if (vnode.data.keepAlive) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
}
},
prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {...},
insert(vnode: MountedComponentVNode) {...},
destroy(vnode: MountedComponentVNode) {...},
};
const hooksToMerge = Object.keys(componentVNodeHooks);
// 創(chuàng)建自定義組件對應(yīng)的 VNode
export function createComponent(
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string,
): VNode | void {
if (isUndef(Ctor)) {
return;
}
const baseCtor = context.$options._base;
// plain options object: turn it into a constructor
// 如果Ctor不是一個構(gòu)造函數(shù)硫惕,是一個對象
// 使用Vue.extend()創(chuàng)造一個子組件的構(gòu)造函數(shù)
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// if at this stage it's not a constructor or an async component factory,
// reject.
// 如果在此階段它不是構(gòu)造函數(shù)或異步組件工廠茧痕,則拒絕。
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context);
}
return;
}
// async component
// 異步組件處理
let asyncFactory;
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor;
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
// 返回異步組件的占位符節(jié)點恼除,該占位符呈現(xiàn)為注釋節(jié)點踪旷,但保留該節(jié)點的所有原始信息。
// 該信息將用于異步服務(wù)器渲染和客戶端激活豁辉。
return createAsyncPlaceholder(asyncFactory, data, context, children, tag);
}
}
data = data || {};
// resolve constructor options in case global mixins are applied after
// component constructor creation
// 解析構(gòu)造函數(shù)選項
// 在組件構(gòu)造函數(shù)創(chuàng)建后合并當(dāng)前組件選項和通過vue.mixin混入的選項
resolveConstructorOptions(Ctor);
// transform component v-model data into props & events
// 將組件v-model數(shù)據(jù)轉(zhuǎn)換為props 和 events
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
// extract props
// 提取props
const propsData = extractPropsFromVNodeData(data, Ctor, tag);
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children);
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot;
data = {};
if (slot) {
data.slot = slot;
}
}
// merge component management hooks onto the placeholder node
// 合并組件的鉤子函數(shù)init/prepatch/insert/destroy
// 準(zhǔn)備好了data.hook中的鉤子函數(shù)
mergeHooks(data);
// return a placeholder vnode
const name = Ctor.options.name || tag;
// 創(chuàng)建自定義組件的VNode令野,設(shè)置自定義組件的名字
// 記錄this.componentOptions = componentOptions
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data,
undefined,
undefined,
undefined,
context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory,
);
return vnode;
}
// 創(chuàng)建組件實例的位置,由自定義組件的 init() 鉤子方法調(diào)用
export function createComponentInstanceForVnode(
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
parentElm?: ?Node,
refElm?: ?Node,
): Component {
const vnodeComponentOptions = vnode.componentOptions;
const options: InternalComponentOptions = {
_isComponent: true,
parent,
propsData: vnodeComponentOptions.propsData,
_componentTag: vnodeComponentOptions.tag,
_parentVnode: vnode,
_parentListeners: vnodeComponentOptions.listeners,
_renderChildren: vnodeComponentOptions.children,
_parentElm: parentElm || null,
_refElm: refElm || null,
};
// check inline-template render functions
// 獲取inline-template
// 例如:<comp inline-template>xx</comp>
const inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
// 創(chuàng)建組件實例
return new vnodeComponentOptions.Ctor(options);
}
function mergeHooks(data: VNodeData) {
if (!data.hook) {
data.hook = {};
}
// 用戶可以傳遞自定義鉤子函數(shù)
// 把用戶傳入的自定義鉤子函數(shù)和componentVNodeHooks中預(yù)定義的鉤子函數(shù)合并
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i];
const fromParent = data.hook[key];
// 獲取鉤子函數(shù)(init()鉤子中創(chuàng)建組件的實例)
const ours = componentVNodeHooks[key];
data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours;
}
}
function mergeHook(one: Function, two: Function): Function {
return function (a, b, c, d) {
one(a, b, c, d);
two(a, b, c, d);
};
}
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel(options, data: any) {
const prop = (options.model && options.model.prop) || 'value';
const event = (options.model && options.model.event) || 'input';
(data.props || (data.props = {}))[prop] = data.model.value;
const on = data.on || (data.on = {});
if (isDef(on[event])) {
on[event] = [data.model.callback].concat(on[event]);
} else {
on[event] = data.model.callback;
}
}
組件實例的創(chuàng)建和掛載過程
Vue._update() --> patch() --> createElm() --> createComponent()
// 注意:先創(chuàng)建父組件再創(chuàng)建子組件徽级;先掛載子組件再掛載父組件彩掐。
// 1.創(chuàng)建組件實例,掛載到真實 DOM
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data;
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
// 調(diào)用init()方法灰追,創(chuàng)建和掛載組件實例
// init()的過程中創(chuàng)建好了組件的真實DOM堵幽,掛載到了vnode.elm上
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */, parentElm, refElm);
}
if (isDef(vnode.componentInstance)) {
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
// 調(diào)用鉤子函數(shù)(VNode的鉤子函數(shù)初始化屬性/事件/樣式等,組件的鉤子函數(shù))
initComponent(vnode, insertedVnodeQueue);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
}
}
// 2.調(diào)用鉤子函數(shù)弹澎,設(shè)置局部作用于樣式
function initComponent(vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
vnode.elm = vnode.componentInstance.$el;
if (isPatchable(vnode)) {
// 調(diào)用鉤子函數(shù)
invokeCreateHooks(vnode, insertedVnodeQueue);
// 設(shè)置局部作用于樣式
setScope(vnode);
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode);
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode);
}
}
// 3.調(diào)用create鉤子函數(shù)
function invokeCreateHooks(vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
// 觸發(fā)create鉤子函數(shù)
cbs.create[i](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
// 調(diào)用組件的鉤子函數(shù)
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode);
if (isDef(i.insert)) insertedVnodeQueue.push(vnode);
}
}