blog

Vueの初期化処理(a)

Vueをよく使うフロントエンドの学生にとって、以下のコード行はとても見慣れたものですが、このコード行が実際に何をしているのかはあまり明確ではありません。 そこで次に、ソースコードを通して、Vueの初期...

Jun 14, 2020 · 9 min. read
シェア

前置き

Vueをよく使うフロントエンドの学生にとって、以下のコード行はとても馴染み深いものですが、このコード行が実際に何をしているのかはあまり明確ではありません。テンプレートコードの初期化

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
</head>
<body>
 <div id="app">{{ name }}</div>
</body>
</html>
new Vue({
	el: '#app',
 	data () {
 	return {
 	name: 'rookie'
 }
 }
})

それでは次に、Vueの初期化の全体的なプロセスを分析するために、ソースコードを順を追って説明します。Vue版本@2.6.x,编译版本。

ビューコンストラクタ

まず、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の()操作の実装は、_initメソッドのインスタンスの実装であり、オプション変数は、実際には外部パスの値であり、次の関数のmixinは、メソッドまたはいくつかの変数をマウントするVue関数のプロトタイプに与えられます。

_init

次に実装、ソースコードを見てみましょう

 /* @flow */
 import config from '../config'
 import { initProxy } from './proxy'
 import { initState } from './state'
 import { initRender } from './render'
 import { initEvents } from './events'
 import { mark, measure } from '../util/perf'
 import { initLifecycle, callHook } from './lifecycle'
 import { initProvide, initInjections } from './inject'
 import { extend, mergeOptions, formatComponentName } from '../util/index'
 
 let uid = 0
 
 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)
 }
 
 // a flag to avoid this being observed
 vm._isVue = true
 // merge options
 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 {
 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) // resolve injections before data/props
 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)
 }
 }
 }
 // ... ignore code

$mount

分析されたバージョンはコンパイルされているので、$mount関数はここでファイルで定義されており、関連するコードは以下の通りです。

 // .. ignore code
 import Vue from './runtime/index'
 import { query } from './util/index'
 const mount = Vue.prototype.$mount
 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 <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
 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, {
 outputSourceRange: process.env.NODE_ENV !== 'production',
 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)
 }
 
 /**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
 function getOuterHTML (el: Element): string {
 if (el.outerHTML) {
 return el.outerHTML
 } else {
 const container = document.createElement('div')
 container.appendChild(el.cloneNode(true))
 return container.innerHTML
 }
 }
 
 Vue.compile = compileToFunctions
 
 export default Vue

Vue.prototypemount会被存储下来,接着重写Vue原型上的mountVuemountが格納され、続いてVueプロトタイプのmountメソッドがオーバーライドされます。関数を実行すると、elの値を受け取って判定を行い、elがbody要素またはhtml要素であれば、Vueはbody要素またはhtml要素の置換を許可していないという警告を例外として投げます。そして、template属性を判定し、一連の判定処理の後、template属性をrender関数に変換し、最後にmountメソッドを呼び出し、対応する実装ここで参照します。

 Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
 ): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
 }

まず、elの値を取りますが、操作の値も非常に簡単で、elが存在するかどうかを判断するために、現在の環境はブラウザで、次の操作では、クエリ関数は、次のように実装されています。

 export function query (el: string | Element): Element {
 if (typeof el === 'string') {
 const selected = document.querySelector(el) // これは、ドキュメント.querySelectorApiのselected要素は
 if (!selected) {
 process.env.NODE_ENV !== 'production' && warn(
 'Cannot find element: ' + el
 )
 return document.createElement('div')
 }
 return selected
 } else {
 return el
 }
 }

elの値を取った後、mountComponentメソッドが実行されます。

マウントコンポーネント


 export function mountComponent (
 vm: Component,
 el: ?Element,
 hydrating?: boolean
 ): Component {
 vm.$el = el
 // ignore code
 callHook(vm, 'beforeMount')
 
 let updateComponent
 /* istanbul ignore if */
 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
 // ignore code
 } else {
 updateComponent = () => { // 一般的には、このブランチに行き、getメソッドの解析に集中する。
 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
 }

まずelがvm.$elに代入され、beforeMountフック関数が実行され、updateComponentがそれに応じて代入され、new Watcher()が実行されます。

Watcher

 /* @flow */
 import {
 warn,
 remove,
 isObject,
 parsePath,
 _Set as Set,
 handleError,
 noop
 } from '../util/index'
 
 import { traverse } from './traverse'
 import { queueWatcher } from './scheduler'
 import Dep, { pushTarget, popTarget } from './dep'
 
 import type { SimpleSet } from '../util/index'
 
 let uid = 0
 
 /**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
 export default class Watcher {
 vm: Component;
 expression: string;
 cb: Function;
 id: number;
 lazy: boolean;
 // ignore code
 
 constructor (
 vm: Component,
 expOrFn: string | Function,
 cb: Function,
 options?: ?Object,
 isRenderWatcher?: boolean
 ) {
 this.vm = vm
 if (isRenderWatcher) {
 vm._watcher = this
 }
 vm._watchers.push(this)
 // options
 if (options) {
 this.deep = !!options.deep
 this.user = !!options.user
 this.lazy = !!options.lazy
 this.sync = !!options.sync
 this.before = options.before
 } else {
 this.deep = this.user = this.lazy = this.sync = false
 }
 // parse expression for getter
 if (typeof expOrFn === 'function') {
 this.getter = expOrFn
 } 
 this.value = this.lazy
 ? undefined
 : this.get()
 }
 
 /**
 * Evaluate the getter, and re-collect dependencies.
 */
 get () {
 pushTarget(this)
 let value
 const vm = this.vm
 try {
 value = this.getter.call(vm, vm)
 } catch (e) {
 if (this.user) {
 handleError(e, vm, `getter for watcher "${this.expression}"`)
 } else {
 throw e
 }
 } finally {
 // "touch" every property so they are all tracked as
 // dependencies for deep watching
 if (this.deep) {
 traverse(value)
 }
 popTarget()
 this.cleanupDeps()
 }
 return value
 }
 }

理解しやすくするために、この実行に必要なコア・コードだけをインターセプトしました。より完全なソースコードはここをクリックして見ることができます。

 new Watcher(vm, updateComponent, noop, {
 before () {
 if (vm._isMounted && !vm._isDestroyed) {
 callHook(vm, 'beforeUpdate')
 }
 }
 }, true /* isRenderWatcher */)

最初のパラメータは vm インスタンス、2 番目のパラメータは updteComponent 関数、3 番目の noop は空の関数、4 番目のパラメータはオブジェクト、5 番目のパラメータは true で、これはウォッチャーをレンダリングすることを意味します。 パラメータを渡した後、新しいウォッチャーは this.get() を実行し、get メソッドを実行すると this.getter が実行され、ウォッチャーをレンダリングするには this.getter を実行します。".get() "を実行し、getメソッドを実行すると this.getterが実行され、ウォッチャーをレンダリングするにはupdateComponentメソッドを実行します。仮想 DOM を生成し、仮想 DOM を実際の DOM にマッピングします。

Vueの初期化プロセスは、ここで最初の分析、updateComponentメソッドの分析は、次の分析に入れ、不備やエラーがある場合は、指摘することを楽しみにしています!

Read next

Java8では、実戦のインターフェース・デフォルト・メソッドの場所を知らないかもしれない。

I. コア機能の概要\n\nインターフェース・デフォルト・メソッド\n\nウェブサイトへの登録時には、登録後にウェブサイトから提供されるデフォルトのアバターを使用し、デフォルトのアイコンは通常、アプリケーションのインストールに成功した後に提供され、デフォルトの住所は、eコマースウェブサイトから購入した商品の代金を支払うときに一般的に使用される配送先として設定され、生活の中でデフォルトの住所になるようです。

Jun 14, 2020 · 10 min read