이번 포스트에서는 2. Initialize - Vue 코어 함수에서 이야기 했던 src/core/instance/index.js 파일의 5개의 mixin에 대해 알아보도록 하겠습니다.
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
src/core/instance/index.js 파일부터 코드를 따라가도록 하겠습니다.
initMixin 함수
initMixin 함수는 src/core/instance/init.js에 정의되어 있습니다.
/* @flow */
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'
let uid = 0
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
...
// 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.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
...
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
...
}
export function resolveConstructorOptions (Ctor: Class<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
}
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
...
}
src/core/instance/init.js 파일 내용입니다. 코드가 조금 길어서, 이야기 할 내용을 제외하고 많이 생략하였습니다.
initMixin함수를 정의 합니다. 이 함수 안에서Vue.prototype._init함수를 정의합니다. 이 함수가src/core/instance/index.js파일에 코어 함수에서 이야기 한this._init함수 입니다. 밑에서 더 자세히 이야기 될 테니 기억해 주세요.initInternalComponent함수를 정의 합니다.initMixin함수에 적힌 주석에 따르면, 동적으로 option을 merge 하는 것은 매우 느리기 때문에 internal component 인스턴스화를 최적화 하기 위해 사용되었다고 합니다.resolveConstructorOptions함수를 정의 합니다. option들을 모으는 함수 입니다.resolveModifiedOptions함수를 정의 합니다. 이 함수는resolveConstructorOptions함수에서 사용되고,resolveConstructorOptions함수에 적힌 주석에 따르면, #4976 hot-reload 버그와 연관 되어 있다고 합니다.
stateMixin 함수
stateMixin 함수는 src/core/instance/state.js에 정의되어 있습니다.
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
}
stateMixin 함수 코드입니다.
dataDef의 getter를 정의합니다.propsDef의 getter를 정의 합니다.- production 모드가 아닐 경우에만
dataDef와propsDef의 setter를 정의 합니다. setter의 역할을 경고 로그를 출력하는 역할만 합니다. Vue.prototype.$data에dataDef를 저장합니다.Vue.prototype.$props에propsDef를 저장합니다.Vue.prototype.$set를 정의합니다.Vue.prototype.$delete를 정의합니다.Vue.prototype.$watch를 정의합니다.
$data, $props, $set, $delete, $watch는 Vue를 사용하면서 한번쯤 보았던 이름들입니다.
Watcher가 사용되는 것을 볼 수 있습니다. Watcher는 data와 view의 동기를 맞춰주는 역할을 합니다.
eventsMixin 함수
eventsMixin 함수는 src/core/instance/events.js에 정의되어 있습니다.
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
...
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
...
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
...
}
Vue.prototype.$emit = function (event: string): Component {
...
}
}
eventsMixin 함수 코드입니다. 세부 내용은 생략하였습니다.
Vue.prototype.$on을 정의합니다.Vue.prototype.$once을 정의합니다.Vue.prototype.$off을 정의합니다.Vue.prototype.$emit을 정의합니다.
Vue를 사용하면서 익숙하게 보아온 이벤트 함수들의 정의가 이 곳에서 일어납니다.
lifecycleMixin 함수
lifecycleMixin 함수는 src/core/instance/lifecycle.js에 정의되어 있습니다.
...
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
}
Vue.prototype.$forceUpdate = function () {
...
}
Vue.prototype.$destroy = function () {
...
}
}
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
...
}
...
src/core/instance/lifecycle.js 코드 부분입니다. 이야기할 부분을 제외하고 모두 생략하였습니다.
lifecycleMixin 함수는
Vue.prototype._update를 정의합니다. DOM 업데이트가 이곳에서 일어 납니다. 9. View Render - Patch에서 자세히 이야기 하도록 하겠습니다.Vue.prototype.$forceUpdate를 정의합니다.Vue.prototype.$destroy를 정의합니다.
lifecycleMixin 함수 바로 아래 mountComponent 함수가 있습니다. 이 함수는 2. Initialize - Vue 코어 함수에서 잠깐 언급 된 적이 있습니다. Vue.prototype.$mount는 mountComponent 함수 호출 값을 리턴합니다. 이전 포스트에서 2번 캡슐화 된다고 이야기 했습니다. 결국 $mount는 mountComponent를 2번 캡슐화 한 함수입니다. mountComponent 함수는 9. View Render - Patch에서 자세히 이야기 할 것입니다.
src/core/instance/lifecycle.js 파일에는 이외의 몇개의 함수가 존재합니다. 이 함수들은 DOM을 업데이트 하는데 사용됩니다.
renderMixin 함수
renderMixin 함수는 src/core/instance/render.js에 정의되어 있습니다.
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
...
}
Vue.prototype._render = function (): VNode {
...
}
}
renderMixin 함수 코드입니다. 이 코드 역시 많이 생략하였습니다.
Vue.prototype.$nextTick함수를 정의합니다.Vue.prototype._render함수를 정의합니다.
요약
Mixin 레이어에서는 Vue.prototype안에 몇가지 함수와 객체들을 정의합니다.
아래 그림은 Mixin들이 정의하는 함수, 객체들의 목록을 나타낸 그림입니다.

- initMixin:
_init()를 정의합니다. - stateMixin:
$data,$props,$set(),$delete(),$watch()를 정의합니다. - eventsMixin:
$on(),$once(),$off(),$emit()를 정의합니다. - lifecycleMixin:
_update(),$forceUpdate(),$destroy()를 정의합니다. - renderMixin:
_render()를 정의합니다.
다음으로 볼 것
src/core/instance/index.js의 코어 함수에서 호출하는 this._init(options)를 살펴보도록 하겠습니다. 4. Initialize - _init 함수에서 확인 할 수 있습니다.