まえがき
Vueのソースコードを読み解く第2弾が始まります。Vueのプロセスをコンポーネント化するための初期化プロセスや、Vnodeの作成方法について説明します。また、vm.$vnodeとvm._vnodeの違いと関係についても説明します。vm._update(vm._render(), hydrating)前回の投稿では、vm.$mount関数が最終的に実行されることについて説明しました。
Vnodeを作成
src/core/instance/render.jsこのコードで最初に見るべきは、vm._render()です。
I.vm._render()あなたは何をしましたか?
まず、vm._render()の定義です。export function renderMixin (Vue: Class<Component>) { //Vueクラスの初期化時にレンダー関数をマウントする
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode //最初のマウントは未定義
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement) //Vnodevmの具体的な実装.$createElement
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it ルートノードは1つしかない。
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out 只能有一个根节
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { //ルートノードは1つしかない。
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
export function renderMixin (Vue: Class<Component>) { //Vue初期クラスの化時にレンダー関數をマウントする
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode //初期モビリティは未定義!
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement) //Vnodevm具体的な実践.$createElement
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it ルートノードは1つしかない。
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out 只能有一个根节
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { //ルートノードは1つしかない。
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}
vm._update(vnode,hydrafting)コードを見るとわかりますが、vm._render()はVueのrenderMixinプロセスで作成され、まず各コードの詳細を見ずに、最終的に返されるvm._render()がvnodeであることがわかります。これらのステップのうちのいくつかに焦点を当てれば、詳細はとりあえず無視できます:
const { render, _parentVnode } = vm.$options1. vm.$optionsからrender関数と父親のvnodeを取得;
vm.$vnode = _parentVnode2、アノテーションは、このノードがレンダー関数がプレースホルダノードデータを取得できるようにすることは非常に明確です。<el-button></el-button>この後のコンポーネントの作成で、プレースホルダノードのデータを取得する方法について説明しますが、vm.$vnodeは父親ノード、つまり、element-uiなどのプレースホルダノードであることが理解できます。
vnode = render.call(vm._renderProxy, vm.$createElement)3、実際には、vnodeはvm.$createElementを通して生成され、vm.$createElementはしばしばh関数として参照されます:
new Vue({
render: h => h(App),
}).$mount('#app')
vm._renderProxyに関しては、vm._init()の中で定義されます。
vm.$createElement
次に、vm.$createElementが何をするのかを見てみましょう。src/core/instance/render.jsこのコードでは、vm._init()を介して、vm.$createElementをinitRender(vm)で初期化しています。
export function initRender (vm: Component) {
//.....some codes
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
//... somecodes
}
createElement(vm, a, b, c, d, true)createElement(vm, a, b, c, d, true)vm.$createElementが実際に結果を返していることがわかります。
第三に、createElementと_createElement
3-1: createElement
src/core/vdom/create-elements.jscreateElement は .NET Framework で定義されています:
export function createElement ( // _createElement機能のカプセル化
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) { //如果未传入data 则把变量值往下传递
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
このパラメータは、vueの公式ドキュメントのcreateElementパラメータで定義されているフィールドです。もちろん、App.vueでエクスポートされたアプリオブジェクトがタグとして渡されます。createElement()メソッドは、実際には、dataパラメータが渡されなかった場合に子要素などを下に移動させるだけで、誤ったパラメータ呼び出しが行われないようにしていることがわかります。本当のロジックは、戻り関数 _createElement にあります。
3-2: _createElement
src/core/vdom/create-elements.jscreateElementに関連する定義は、:
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}
` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
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
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
コードは長いので、いくつかの重要なポイントに集中してください:
1、childrenパラメータ処理:childrenパラメータは、決定するために入力パラメータのnormalisationTypeの値に応じて、2つの処理ケースを持っています。normalizeChildren(children)simpleNormalizeChildren(children)それはALWAYS_NORMALIZEコールである場合、それはSIMPLE_NORMALIZEである場合、呼び出し 。./helper/normolize-children.jsこれらの2つの関数は、下のディレクトリの同じレベルで定義されている、ここでは言うために詳細に展開しない、自分の理解を見てみましょう、1つは直接concat()シュートフラット配列であり、1つは配列に再帰的なプッシュです。
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
2、タグのタイプを決定する:1、文字列型であれば、ネイティブのhtmlタグかどうかを判断し続け、そうであれば、新しいVnode()、そうでなければ、タグはvue.component()またはローカルのコンポーネントコンポーネントを介して登録され、コンポーネントVnodeを生成するためにcreateComponent()に移動します;2、文字列型でない場合は、上記の状況と同様で、渡されたオブジェクトがAppの場合は、createComponent()メソッドでvnodeを生成します:
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
//生産環境
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
//不明な名前空間
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
3、Vnodeを返します。
createComponent(tag, data, context, children)ここでvnodeが生成されます。
createComponent()
export function createComponent (
Ctor: Class<Component> | Function | Object | void, //着信オブジェクト
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base //は、基本クラスのコンストラクタとして、Vueそのものである。
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor) // Vueを理解するためにコンストラクタに変換する.extend
}
// if at this stage it's not a constructor or an async component factory,
// reject.
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)
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.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract 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
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data) //
// return a placeholder vnode
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://.//--///nt
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
これらのハイライトのいくつかに注目してください:
つまり、Vue.extend() が行うことです。
Ctor = baseCtor.extend(Ctor)vue自身を参照する最初のレンダリングは、次の場所で定義されています。では、vue.extend()は、appオブジェクトに対して具体的に何を行うのでしょうか。vue.extendは、次の場所で定義されています:
Vue.cid = 0
let cid = 1
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this //Vueの静的メソッドを指す
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) { //单例模式,如果是复用的组件,那么 直接返回缓存在cachedCtors[SuperId]サブ
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name) // コンポーネントのname属性をチェックする
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype) //プロトタイプの継承
Sub.prototype.constructor = Sub //ポイント・コンストラクタをサブに戻す。
Sub.cid = cid++
Sub.options = mergeOptions( //マージオプション
Super.options,
extendOptions
)
Sub['super'] = Super //superVueを指す
// 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.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
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.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
Vue.extend()が行うことは、おおよそ以下の5つです。
cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})1.パフォーマンスを最適化するために、このコンポーネントインスタンスのコンストラクタ・メソッド属性を作成します:
if (cachedCtors[SuperId]) { //单例模式,如果是复用的组件,那么 直接返回缓存在cachedCtors[SuperId]サブ
return cachedCtors[SuperId]
}
2.コンポーネントのコンストラクタを作成するプロトタイプ継承パターン:
const Sub = function VueComponent (options) {
this._init(options)//Vueを探す_init
}
Sub.prototype = Object.create(Super.prototype) //プロトタイプの継承
Sub.prototype.constructor = Sub //ポイント・コンストラクタをサブに戻す。
Sub.cid = cid++
Sub.options = mergeOptions( //マージオプション
Super.options,
extendOptions
)
Sub['super'] = Super //superVueを指す
object.defineProperty3.パッチのインスタンス化がここを呼び出さないように、双方向のデータバインディングを進めます:
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
4.Vueの親クラスのグローバルメソッドを継承します:
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
5.オブジェクトをキャッシュして返します:
cachedCtors[SuperId] = Sub
return Sub
createComponentあとは
const propsData = extractPropsFromVNodeData(data, Ctor, tag)1、このコードは、プレースホルダ・ノードからカスタムpropsプロパティを取得し、後でそれをサブコンポーネントに渡すための鍵となります。
2.機能部品:
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
installComponentHooks(data)3は、別のキーは、フック関数の上に登録されたコンポーネントは、フック関数は、以下の同じファイルに定義されています:initに分かれて、プリパッチ、挿入、4つのフック関数を破棄し、パッチプロセスが具体的に導入されます:
const hooksToMerge = Object.keys(componentVNodeHooks)
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
4, new Vnode() & retrun vnode.
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
コンポーネントのコンストラクタなどが、コンストラクタが渡されたオブジェクトの中に置かれているのがわかります。
つまり、この時点でcreateComponent()の処理はおおよそ終わっています。
Virtual-Dom
vnodeの作成については半日話しましたが、vnodeクラスの実装については話しませんでしたが、最後にvnodeの話をしたのには理由があり、vnodeを作成するには多すぎる状況であり、上記をまず広め、最後にvnodeを集めて、犬の印象を深めることができればと思います。
5-1.なぜVnodeを使うのですか?
1、本物のDOMを作るための高いコスト
実際のDOMノードが多くの属性を実装しているのに対し、vnodeは必要な一部の属性のみを実装しており、vnodeを作成するコストは比較的低くなっています。そのため、vnodeを作成することで、要件そのものにより集中することができます。
2、複数のブラウザの再描画と再フローのトリガー
vnodeの使用は、バッファを追加するのと同等なので、すべてのノードの変更によってもたらされるデータの変更は、最初のvnodeで変更され、その後、ブラウザの再描画とリフローを減らすために、変更するには、DOMツリーに一度集中ノード内のすべての差の後に差分。これは、キューを更新するウォッチャーのレンダリング、コンポーネントの更新など、詳細な応答の原理の章で詳細に説明されます〜〜。
-2中間マージンなし、Class Vnode{}
src/core/vdom/vnode.jsVnode クラスは .NET で定義されています:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
//somecodes
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
このVnodeクラスは非常に長くなっているようですが、実際のdom nodeはこれよりも複雑で巨大であり、その属性は、当面は気にすることができないものもありますが、コンストラクタに焦点を当てています:
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
createComponent() で作成された vnode に渡されたパラメータを比較します:
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
概要
"このセクションの焦点は、vnodeの作成方法と、状況によって異なる作成方法です。 今は詳細を掘り下げてはいけませんが、後々徐々に広げていくことで、振り返った時にスッキリと理解できるようになります!振り返ればわかるようになる!"
最後にちょっと宣伝。
SMSビジネスや番号認証機能が必要なお偉方は、こちらをご覧ください! 事業者の公式プラットフォーム!中間マージンなし





