Vue インスタンスをマウントするにはどうすればよいですか?次の記事では、Vue インスタンスのマウントのプロセスについて説明します。お役に立てば幸いです。
Vue2 は長い間注目されており、Vue3 さえも使用され始めています。誰もが Vue2 を徐々に習得する必要があります。さらに深く考えると、たとえ大手メーカーと関わりたくないとしても、ソースコードレベルに到達するよう努めるべきです。 Vueインスタンスのマウント処理はインタビューでもよく登場するテストポイントですが、今回はソースコードをもとにステップバイステップで分析していきます! (学習ビデオ共有: vuejs チュートリアル)
誰もが聞いたことがある知っている、知っているそれじゃあこの文。
では、new Vue()
について考えたことがあるかどうかはわかりませんが、このプロセスでは正確に何が行われるのでしょうか?
プロセスでは、データのバインドを完了する方法、データをビューにレンダリングする方法などを説明します。
最初に Vue
のコンストラクターを見つけます。ソース コードの場所: src/core/インスタンス /index.js
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) }
options
は、data、methods
、およびその他の一般的に使用されるメソッドなど、ユーザーによって渡される構成項目です。
Vue
構築関数は _init
メソッドを呼び出しますが、このメソッドはこのファイルには存在しないことがわかりましたが、よく見ると次のことがわかります。ファイルの最後には多くの初期化メソッドが定義されています。
initMixin(Vue); // 定义 _init stateMixin(Vue); // 定义 $set $get $delete $watch 等 eventsMixin(Vue); // 定义事件 $on $once $off $emit lifecycleMixin(Vue);// 定义 _update $forceUpdate $destroy renderMixin(Vue); // 定义 _render 返回虚拟dom
まず、initMixin
メソッドを確認すると、このメソッドが Vue
プロトタイプの _init
メソッドを定義していることがわかります。
ソース コードの場所: src/core/instance/init.js
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) } // a flag to avoid this being observed vm._isVue = true // merge options // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法 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 { // 合并vue属性 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // 初始化proxy拦截器 initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 初始化组件生命周期标志位 initLifecycle(vm) // 初始化组件事件侦听 initEvents(vm) // 初始化渲染方法 initRender(vm) callHook(vm, 'beforeCreate') // 初始化依赖注入内容,在初始化data、props之前 initInjections(vm) // resolve injections before data/props // 初始化props/data/method/watch/methods 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) { vm.$mount(vm.$options.el) } }
上記のコードを注意深く読むと、次の結論が得られます:
beforeCreate
を呼び出す前に、データの初期化が完了していません。data
や props
などのプロパティにはアクセスできません。 ## 作成時 および
props にアクセスできますが、
dom のマウントは完了していませんしたがって、
dom 要素にはアクセスできません。
マウント方法は、
vm.$mount
props/ data/method/ watch/methods の初期化。
ソースコードの場所:
src/core/instance/state.js
export function initState (vm: Component) { // 初始化组件的watcher列表 vm._watchers = [] const opts = vm.$options // 初始化props if (opts.props) initProps(vm, opts.props) // 初始化methods方法 if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { // 初始化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## の初期化方法を見ていきます。 # initData (これは initState
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">function initData (vm: Component) {
let data = vm.$options.data
// 获取到组件上的data
data = vm._data = typeof data === &#39;function&#39;
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== &#39;production&#39; && warn(
&#39;data functions should return an object:\n&#39; +
&#39;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&#39;,
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 !== &#39;production&#39;) {
// 属性名不能与方法名重复
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 属性名不能与state名称重复
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== &#39;production&#39; && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) { // 验证key值的合法性
// 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据
proxy(vm, `_data`, key)
}
}
// observe data
// 响应式监听data是数据的变化
observe(data, true /* asRootData */)
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
と同じファイル上にあります) 上記のコードを注意深く読むと、次の結論が得られます:
props
,data
data
(コンポーネントを定義するときに関数形式またはオブジェクト形式を選択できます) Functional 形式のみ可能です) データの応答性については、ここでは詳細な説明は行いません。
前述したように、マウント方法は
vm.$mount ソース コード: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 获取或查询元素
el = el && query(el)
/* istanbul ignore if */
// vue 不允许直接挂载到body或页面文档上
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== &#39;production&#39; && warn(
`Do not mount Vue to <html> or <body> - 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
// 存在template模板,解析vue模板文件
if (template) {
if (typeof template === &#39;string&#39;) {
if (template.charAt(0) === &#39;#&#39;) {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== &#39;production&#39; && !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 !== &#39;production&#39;) {
warn(&#39;invalid template option:&#39; + template, this)
}
return this
}
} else if (el) {
// 通过选择器获取元素内容
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
mark(&#39;compile&#39;)
}
/**
* 1.将temmplate解析ast tree
* 2.将ast tree转换成render语法字符串
* 3.生成render方法
*/
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== &#39;production&#39;,
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
mark(&#39;compile end&#39;)
measure(`vue ${this._name} compile`, &#39;compile&#39;, &#39;compile end&#39;)
}
}
}
return mount.call(this, el, hydrating)
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
上記のコードを読むと、次の結論が得られます。
body
または
オブジェクトに定義できますtemplate/render
または直接使用しますel
は、要素セレクター
が最終的に render
関数に解析され、呼び出しが行われることを意味します。 template
を render
関数に解析します
templatehtml
ast
記述子を文字列に解析render
Function
render にマウントした後、 mount
メソッドが再度呼び出されます ソース コードの場所:
src/platforms/web/runtime/index.js
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined // 渲染组件 return mountComponent(this, el, hydrating) }
Calling mountComponent
レンダリング コンポーネント
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el // 如果没有获取解析的render函数,则会抛出警告 // render是解析模板文件生成的 if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { // 没有获取到vue的模板文件 warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 执行beforeMount钩子 callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { // 定义更新函数 updateComponent = () => { // 实际调⽤是在lifeCycleMixin中定义的_update和renderMixin中定义的_render vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined // 监听当前组件状态,当有数据变化时,更新组件 new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { // 数据更新引发的组件更新 callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
上記のコードを読むと、次の結論が得られます:
beforeCreate
フックページ ビューをレンダリングするメソッド
ライフ フック
updateComponent
メソッドは主に render、
update メソッド
render
が主に使用されます。
vnode
ソース コードを生成する場所:
src/core/instance/render.js
// 定义vue 原型上的render方法 Vue.prototype._render = function (): VNode { const vm: Component = this // render函数来自于组件的option 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 // 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNode vnode = render.call(vm._renderProxy, vm.$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 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)) { 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 }
_updateメイン関数は、
patch
vnode## を挿入します。 #実数のDOM
に変換し、ページソース コードの場所:
src に更新します。 /core/instance/lifecycle.js
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
// 设置当前激活的作用域
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
// 执行具体的挂载逻辑
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent&#39;s updated hook.
}</pre><div class="contentsignin">ログイン後にコピー</div></div><h2><strong>三、结论</strong></h2>
<ul>
<li>
<code>new Vue
的时候会调用_init
方法
$set
、$get
、$delete
、$watch
等方法$on
、$off
、$emit
、$off
等事件_update
、$forceUpdate
、$destory
生命周期$mount
进行页面的挂载mountComponent
方法updateComponent
更新函数render
生成虚拟DOM
_update
将虚拟DOM
生成真实DOM
结构,并且渲染到页面中以上がVue インスタンスをマウントするにはどうすればよいですか?インスタンスのマウントのプロセスについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。