ホームページ  >  記事  >  ウェブフロントエンド  >  Vue インスタンスをマウントするにはどうすればよいですか?インスタンスのマウントのプロセスについて話しましょう

Vue インスタンスをマウントするにはどうすればよいですか?インスタンスのマウントのプロセスについて話しましょう

青灯夜游
青灯夜游転載
2022-01-07 19:33:323607ブラウズ

Vue インスタンスをマウントするにはどうすればよいですか?次の記事では、Vue インスタンスのマウントのプロセスについて説明します。お役に立てば幸いです。

Vue インスタンスをマウントするにはどうすればよいですか?インスタンスのマウントのプロセスについて話しましょう

Vue2 は長い間注目されており、Vue3 さえも使用され始めています。誰もが Vue2 を徐々に習得する必要があります。さらに深く考えると、たとえ大手メーカーと関わりたくないとしても、ソースコードレベルに到達するよう努めるべきです。 Vueインスタンスのマウント処理はインタビューでもよく登場するテストポイントですが、今回はソースコードをもとにステップバイステップで分析していきます! (学習ビデオ共有: vuejs チュートリアル)

1. 思考

誰もが聞いたことがある知っている、知っているそれじゃあこの文。

では、new Vue()について考えたことがあるかどうかはわかりませんが、このプロセスでは正確に何が行われるのでしょうか?

プロセスでは、データのバインドを完了する方法、データをビューにレンダリングする方法などを説明します。

2. 分析

最初に 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 を呼び出す前に、データの初期化が完了していません。dataprops などのプロパティにはアクセスできません。 ## 作成時
  • 、データは初期化されており、属性
  • data および props にアクセスできますが、dom のマウントは完了していませんしたがって、dom 要素にはアクセスできません。 マウント方法は、vm.$mount
  • メソッドを呼び出すことです
  • initState
メソッドはcomplete

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<pre class="brush:js;toolbar:false;">function initData (vm: Component) { let data = vm.$options.data // 获取到组件上的data data = vm._data = typeof data === &amp;#39;function&amp;#39; ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; warn( &amp;#39;data functions should return an object:\n&amp;#39; + &amp;#39;https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function&amp;#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 !== &amp;#39;production&amp;#39;) { // 属性名不能与方法名重复 if (methods &amp;&amp; hasOwn(methods, key)) { warn( `Method &quot;${key}&quot; has already been defined as a data property.`, vm ) } } // 属性名不能与state名称重复 if (props &amp;&amp; hasOwn(props, key)) { process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; warn( `The data property &quot;${key}&quot; 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> と同じファイル上にあります) 上記のコードを注意深く読むと、次の結論が得られます:

初期化sequence:

props

,
    methods
  • , datadata(コンポーネントを定義するときに関数形式またはオブジェクト形式を選択できます) Functional 形式のみ可能です)
  • データの応答性については、ここでは詳細な説明は行いません。 前述したように、マウント方法は vm.$mount
  • メソッドを呼び出すことです。

ソース コード: <pre class="brush:js;toolbar:false;">Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { // 获取或查询元素 el = el &amp;&amp; query(el) /* istanbul ignore if */ // vue 不允许直接挂载到body或页面文档上 if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; warn( `Do not mount Vue to &lt;html&gt; or &lt;body&gt; - 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 === &amp;#39;string&amp;#39;) { if (template.charAt(0) === &amp;#39;#&amp;#39;) { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; !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 !== &amp;#39;production&amp;#39;) { warn(&amp;#39;invalid template option:&amp;#39; + template, this) } return this } } else if (el) { // 通过选择器获取元素内容 template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; config.performance &amp;&amp; mark) { mark(&amp;#39;compile&amp;#39;) } /** * 1.将temmplate解析ast tree * 2.将ast tree转换成render语法字符串 * 3.生成render方法 */ const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== &amp;#39;production&amp;#39;, shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== &amp;#39;production&amp;#39; &amp;&amp; config.performance &amp;&amp; mark) { mark(&amp;#39;compile end&amp;#39;) measure(`vue ${this._name} compile`, &amp;#39;compile&amp;#39;, &amp;#39;compile end&amp;#39;) } } } return mount.call(this, el, hydrating) }</pre> 上記のコードを読むと、次の結論が得られます。

ルート要素を

body

または
    html
  • オブジェクトに定義できますtemplate/render または直接使用します
  • template
  • el は、要素セレクター が最終的に render 関数に解析され、呼び出しが行われることを意味します。
  • compileToFunctions
  • templaterender 関数に解析しますtemplate
  • を解析する手順は大まかに分けられます

html

ドキュメントのフラグメントを
    ast
  • 記述子に解析ast 記述子を文字列に解析
  • GeneraterenderFunction
  • render
  • 関数を生成します。
vm

にマウントした後、 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 !== &#39;production&#39;) {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== &#39;#&#39;) ||
        vm.$options.el || el) {
        warn(
          &#39;You are using the runtime-only build of Vue where the template &#39; +
          &#39;compiler is not available. Either pre-compile the templates into &#39; +
          &#39;render functions, or use the compiler-included build.&#39;,
          vm
        )
      } else {
        // 没有获取到vue的模板文件
        warn(
          &#39;Failed to mount component: template or render function not defined.&#39;,
          vm
        )
      }
    }
  }
  // 执行beforeMount钩子
  callHook(vm, &#39;beforeMount&#39;)

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== &#39;production&#39; && 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&#39;s constructor
  // since the watcher&#39;s initial patch may call $forceUpdate (e.g. inside child
  // component&#39;s mounted hook), which relies on vm._watcher being already defined
  // 监听当前组件状态,当有数据变化时,更新组件
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 数据更新引发的组件更新
        callHook(vm, &#39;beforeUpdate&#39;)
      }
    }
  }, 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, &#39;mounted&#39;)
  }
  return vm
}

上記のコードを読むと、次の結論が得られます:

beforeCreate

フック
  • define ## をトリガーします。 #updateComponentページ ビューをレンダリングするメソッド
  • コンポーネント データをリッスンし、変更されたら、
  • beforeUpdateライフ フック
  • updateComponent メソッドは主に
  • vue
初期化中に実行されます。 宣言された

renderupdate メソッド 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&#39;s no need to maintain a stack because all render fns are called
        // separately from one another. Nested component&#39;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 !== &#39;production&#39; && 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 !== &#39;production&#39; && Array.isArray(vnode)) {
            warn(
                &#39;Multiple root nodes returned from render function. Render function &#39; +
                &#39;should return a single root node.&#39;,
                vm
            )
        }
        vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
}

_updateメイン関数は、patch

を呼び出し、

vnode## を挿入します。 #実数のDOMに変換し、ページソース コードの場所: src に更新します。 /core/instance/lifecycle.js<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 &amp;&amp; vm.$parent &amp;&amp; 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&amp;#39;s updated hook. }</pre><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结构,并且渲染到页面中
  • (学习视频分享:web前端开发编程入门

    以上がVue インスタンスをマウントするにはどうすればよいですか?インスタンスのマウントのプロセスについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。