前言 今天我們來解密下init.ts中的代碼內容,並結合 vue 生命周期來分析下 vue 的初始化; GitHub github page 內容 init.ts import config from '../config' import { initProxy } from './proxy' i ...
前言
今天我們來解密下init.ts
中的代碼內容,並結合 vue 生命周期來分析下 vue 的初始化;
內容
init.ts
import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'
import type { Component } from 'types/component'
import type { InternalComponentOptions } from 'types/options'
import { EffectScope } from 'v3/reactivity/effectScope'
// vue 實例id
let uid = 0
export function initMixin(Vue: typeof Component) {
// 接收實例化傳入的參數
Vue.prototype._init = function (options?: Record<string, any>) {
//vue 實例
const vm: Component = this
// 每個vue實例都有對應的一個實例id
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// 代碼覆蓋率測試
if (__DEV__ && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to mark this as a Vue instance without having to do instanceof
// check
// 標記作為vue的實例不必去執行instanceof
vm._isVue = true
// avoid instances from being observed
// 避免實例被observed觀察
vm.__v_skip = true
// effect scope
// 影響範圍
vm._scope = new EffectScope(true /* detached */)
vm._scope._vm = true
// merge options
// 合併參數
// 判斷是否是子組件
if (options && options._isComponent) {
// optimize internal component instantiation
// 優化內部組件實例化
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
// 由於動態選項合併非常緩慢,並且沒有一個內部組件選項需要特殊處理。
// 傳入vue實例併進行組件初始化
initInternalComponent(vm, options as any)
} else {
// 根組件配置 | 合併參數
vm.$options = mergeOptions(
// 解析構造函數參數
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
/* istanbul ignore else */
// 代碼覆蓋測試
if (__DEV__) {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 核心的核心
// 初始化生命周期
initLifecycle(vm)
// 初始化事件監聽
initEvents(vm)
// 初始化渲染
initRender(vm)
// 調用生命周期的鉤子函數 | beforeCreate
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// https://v2.cn.vuejs.org/v2/api/#provide-inject
// 在data/props前進行inject
initInjections(vm) // resolve injections before data/props
initState(vm)
// https://v2.cn.vuejs.org/v2/api/#provide-inject
// provide 父組件提供的數據
// inject 子組件進行註入後直接使用
// 在data/props後進行provide
initProvide(vm) // resolve provide after data/props
// 調用生命周期鉤子函數 | created
callHook(vm, 'created')
/* istanbul ignore if */
// 代碼覆蓋測試
if (__DEV__ && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// 組件如果設置了el則掛載到指定的el上
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
/**
* 初始化內部組件
*
* @param vm
* @param options
*/
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
const opts = (vm.$options = Object.create((vm.constructor as any).options))
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions!
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
/**
* 解析構造函數的選項
*
* @param Ctor
* @returns
*/
export function resolveConstructorOptions(Ctor: typeof Component) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
/**
* 解析修改的選項
*
* @param Ctor
* @returns
*/
function resolveModifiedOptions(
Ctor: typeof Component
): Record<string, any> | null {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = latest[key]
}
}
return modified
}
Demo 演示
demo 位於
example/docs/01.lifecycle.html
通過debugger
的方式,能夠更直觀的查看到整個調用的過程;這裡羅列了選項式 api 和組合式 api,後續的 demo 都會以組合式 api 為主。
具體的 debugger 方法可以查看微軟的文檔devtools-guide-chromium,一般來說 F9 進行調試即可;如果你想跳過某一函數,那就 F10;
<script src="../../dist/vue.js"></script>
<div id="app">{{msg}}</div>
<script>
debugger
// Options API || 設置了el
// var app = new Vue({
// el: '#app',
// data: {
// msg: 'Hello Vue!'
// },
// beforeCreate() {
// console.log('beforeCreate')
// },
// created() {
// console.log('created')
// },
// beforeMount() {
// console.log('beforeMount')
// },
// mounted() {
// console.log('mounted')
// }
// })
// Options API || 手動$mount
// new Vue({
// data: () => ({
// msg: 'helloWord'
// }),
// beforeCreate: () => {
// console.log('beforeCreate')
// },
// created: () => {
// console.log('created')
// },
// beforeMount: () => {
// console.log('beforeMount')
// },
// mounted: () => {
// console.log('mounted')
// }
// }).$mount('#app')
// Composition API
const { ref, beforeCreate, created, beforeMount, mounted } = Vue
new Vue({
setup(props) {
const msg = ref('Hello World!')
return { msg }
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
}
}).$mount('#app')
</script>
內容總結
這裡我們總結下
init.ts
中大致的內容
- 生成 vue 實例 Id;
- 標記 vue 實例;
- 如果是子組件則傳入 vue 實例和選項並初始化組件,否則則進行選項參數合併,將用戶傳入的選項和構造函數本身的選項進行合併;
- 初始化實例生命周期相關屬性,如:$parent、$root、$children、$refs 等;
- 初始化組件相關的事件監聽,父級存在監聽事件則掛載到當前實例上;
- 初始化渲染,如:$slots、$scopedSlots、$createElement、$attrs、$listeners;
- 調用
beforeCreate
生命周期鉤子函數 - 初始化註入數據,在 data/props 之前進行 inject,以允許一個祖先組件向其所有子孫後代註入一個依賴(說白了就是有個傳家寶,爺爺要想傳給孫子,那就要爸爸先 inject,再給兒子)
- 初始化狀態,如:props、setup、methods、data(|| observe)、computed、watch
- 在 data/props 之後進行 provide
- 調用
created
生命周期鉤子函數,完成初始化 - 如果設置了
el
則自動掛載到對應的元素上,不然就要自己$mount
;