vueソース説明シリーズの1つ---宣言レンダリングnew Vue()プロセスで何が起こったか

42606 ワード

vueソース解析
宣言レンダリングはテンプレート構文で宣言的にデータを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仮想ノードの実現