vueソース説明シリーズの1つ---宣言レンダリングnew Vue()プロセスで何が起こったか
42606 ワード
vueソース解析
宣言レンダリングはテンプレート構文で宣言的にデータをDOMにレンダリングする
データはDOMと関連付けられ、データは応答式である.new Vueで発生したnewキーワードはインスタンスを作成するので、Vueは構造関数です.vueソースファイルを開きcore/instance/indexを見つけます.jsファイル、以下のコードが見えます.これが探しているVue関数です.
まず必要なモジュールを導入し,次にVueメソッドを宣言した.Vueメソッドは、まず、現在が本番環境であるかどうかを判断し、newキーワードでVueインスタンスを作成しているかどうかを判断します(newインスタンスがある場合、thisはインスタンスを指し、インスタンスinstanceof Vueはtrueを返します).そして呼び出し_Initメソッド.Initのoptionsパラメータは、上記
initMixinメソッドを呼び出し、initMixinメソッドを表示します.前のflow構文を参照してください.詳細はvuejsソースコードからflowを説明します.js開始-JavaScript静的タイプ検出ツール
initMixinはクラスをパラメータとして受信し、クラス(Vue)のプロトタイプに_を追加しました.Initメソッド.InitはオプションのインタフェースがObjectであるoptionsパラメータを受信する.ここでInitメソッドでは,主に初期化(vm,ライフサイクル,イベントなど)と合併optionsである.
vueプロジェクトの初期化時にruntimeバージョンを選択したと仮定し、まずentry-runtime-with-compilerを見てみましょう.js,ここではelがインスタンスをappノードにマウントすると判断する動作,m o u n tを見ることができる.mount. mount.mountはVueプロトタイプ上に作成され、elがbodyとHTMLラベルであるかどうかを判断し、また本番環境ではbodyとHTMLラベルにテンプレートをマウントできないことを警告します.optionsにrenderがなければ検出を開始elが文字列である場合、#の先頭であるかどうかを検出し、このDOM要素が環境と存在しないかによってテンプレートがあるかどうかを判断します.elが文字列でない場合、ユーザが作成したテンプレートがDOMノードであるかどうかを検出する(document.getElementByID(’#id’).最後の結果を処理するのはtemplate変数に付与され、render関数に変換されます.【詳しくは次のrenderをご覧ください】
上記idToTemplateメソッドでDOM要素が存在しないと判断したのは、主にquerySelectorが検索したdom要素を用いたqueryメソッドである.
props,methods,data,computed,watchを初期化するいくつかの方法を呼び出すinitStateメソッドを見つけた.
次に、data(){}でdataデータを追加したり、data:{}で追加したりすることができるため、まずdataが関数であるかどうかを判断するinitDataメソッドを見つけました.関数の場合はgetDataメソッドを呼び出し、FunctionのためのインタフェースのdataパラメータとVueインスタンスを受信し、dataのthisコンテキストを変更してdataを返します.
これでnew Vueを実行する上で主に何が起こったのかがわかりました.
関連リンク:vuejsソースコード説明flow.js開始-JavaScript静的タイプ検出ツール
vueソース説明シリーズの二------render()関数、VNode仮想ノードの実現
宣言レンダリングはテンプレート構文で宣言的にデータをDOMにレンダリングする
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
データはDOMと関連付けられ、データは応答式である.new Vueで発生したnewキーワードはインスタンスを作成するので、Vueは構造関数です.vueソースファイルを開きcore/instance/indexを見つけます.jsファイル、以下のコードが見えます.これが探しているVue関数です.
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
まず必要なモジュールを導入し,次にVueメソッドを宣言した.Vueメソッドは、まず、現在が本番環境であるかどうかを判断し、newキーワードでVueインスタンスを作成しているかどうかを判断します(newインスタンスがある場合、thisはインスタンスを指し、インスタンスinstanceof Vueはtrueを返します).そして呼び出し_Initメソッド.Initのoptionsパラメータは、上記
{el: '#app',data: {message: 'Hello Vue!'}}
の部分である.initMixinメソッドを呼び出し、initMixinメソッドを表示します.前のflow構文を参照してください.詳細はvuejsソースコードからflowを説明します.js開始-JavaScript静的タイプ検出ツール
initMixinはクラスをパラメータとして受信し、クラス(Vue)のプロトタイプに_を追加しました.Initメソッド.InitはオプションのインタフェースがObjectであるoptionsパラメータを受信する.ここでInitメソッドでは,主に初期化(vm,ライフサイクル,イベントなど)と合併optionsである.
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
//
vm._isVue = true
// options
if (options && options._isComponent) {
//
// ,
// 。
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // data/pops
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) { // el, $mount Dom
vm.$mount(vm.$options.el)
}
}
}
vueプロジェクトの初期化時にruntimeバージョンを選択したと仮定し、まずentry-runtime-with-compilerを見てみましょう.js,ここではelがインスタンスをappノードにマウントすると判断する動作,m o u n tを見ることができる.mount. mount.mountはVueプロトタイプ上に作成され、elがbodyとHTMLラベルであるかどうかを判断し、また本番環境ではbodyとHTMLラベルにテンプレートをマウントできないことを警告します.optionsにrenderがなければ検出を開始elが文字列である場合、#の先頭であるかどうかを検出し、このDOM要素が環境と存在しないかによってテンプレートがあるかどうかを判断します.elが文字列でない場合、ユーザが作成したテンプレートがDOMノードであるかどうかを検出する(document.getElementByID(’#id’).最後の結果を処理するのはtemplate変数に付与され、render関数に変換されます.【詳しくは次のrenderをご覧ください】
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)+
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to or - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
上記idToTemplateメソッドでDOM要素が存在しないと判断したのは、主にquerySelectorが検索したdom要素を用いたqueryメソッドである.
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
return document.createElement('div')
}
return selected
} else {
return el
}
}
props,methods,data,computed,watchを初期化するいくつかの方法を呼び出すinitStateメソッドを見つけた.
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
次に、data(){}でdataデータを追加したり、data:{}で追加したりすることができるため、まずdataが関数であるかどうかを判断するinitDataメソッドを見つけました.関数の場合はgetDataメソッドを呼び出し、FunctionのためのインタフェースのdataパラメータとVueインスタンスを受信し、dataのthisコンテキストを変更してdataを返します.
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:
' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
これでnew Vueを実行する上で主に何が起こったのかがわかりました.
関連リンク:vuejsソースコード説明flow.js開始-JavaScript静的タイプ検出ツール
vueソース説明シリーズの二------render()関数、VNode仮想ノードの実現