vue的虚拟dom返回html,Vue源码分析——虚拟dom如何渲染成真实dom

今天我们来说下vue实例的$mount中都发生了什么。$mount是Vue原型上的方法,是Vue实例化的最后一步。$mount分为带编译器版本和不带编译器版本。我们以下面的代码为例,来讲下在$mount时都发生了什么。

实例代码如下(来源于codesandbox的默认vue项目代码):1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74import Vue from "vue";

import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({

render: h => h(App)

}).$mount("#app");

// app.vue

import HelloWorld from "./components/HelloWorld";

export default {

name: "App",

components: {

HelloWorld

}

};

#app {

font-family: "Avenir", Helvetica, Arial, sans-serif;

-webkit-font-smoothing: antialiased;

-moz-osx-font-smoothing: grayscale;

text-align: center;

color: #2c3e50;

margin-top: 60px;

}

// HelloWorld.vue

{{ msg }}

Installed CLI Plugins

export default {

name: "HelloWorld",

props: {

msg: String

}

};

h3 {

margin: 40px 0 0;

}

ul {

list-style-type: none;

padding: 0;

}

li {

display: inline-block;

margin: 0 10px;

}

a {

color: #42b983;

}

$mount是vue实例化中不可缺少的一部分,它将template转化成虚拟dom,然后根据虚拟dom渲染真实dom节点并进行挂载。在这个过程中,主要讲以下几点:

mountComponent中都发生了什么

渲染watcher(renderWatcher)的执行过程

entry-runtime.js和entry-runtime-with-compiler.js的区别及适用场景

1. mountComponent中发生了什么?

在Vue实例化完成的最后一步(即_init原型方法中),如果初始化的参数里有el,则自动调用$mount方法。否则,需要手动调用$mount方法并传入挂载节点。

实例代码中,是在new Vue()之后手动调用了$mount方法,并传入了App组件。下面找到$mount方法的定义:

1

2

3

4

5

6

7

8

9

10

11

12// src/platforms/web/runtime/index.js

Vue.prototype.$mount = function (

el?: string | Element,

hydrating?: boolean

): {

el = el && inBrowser ? query(el) : undefined

/**

* el: {name: "App", components: {…}, render: ƒ, staticRenderFns: Array(0), _compiled: true, ...rest}

**/

return mountComponent(this, el, hydrating)

}

由上可知,$mount获取到传入的App, 并且去query。在query中,由于App经过vue-loader编译后是一个Object,所以在query中被直接返回了。所以然后紧接着调用mountComponent函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56// src/core/instance

export function mountComponent (

vm: Component,

el: ?Element,

hydrating?: boolean

): {

vm.$el = el

// 1. 检查vm.$options.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 {

warn(

'Failed to mount component: template or render function not defined.',

vm

)

}

}

}

// 2. 调用beforeMount生命周期钩子

callHook(vm, 'beforeMount')

let updateComponent

updateComponent = () => {

// mountComponent的核心

vm._update(vm._render(), hydrating)

}

// 3. 生成渲染watcher

new Watcher(vm, updateComponent, noop, {

before () {

// 如果当前vm实例已经被挂载,并且没有被销毁,调用beforeMount生命周期钩子

if (vm._isMounted && !vm._isDestroyed) {

callHook(vm, 'beforeUpdate')

}

}

}, true /* isRenderWatcher */)

hydrating = false

if (vm.$vnode == null) {

vm._isMounted = true

callHook(vm, 'mounted')

}

return vm

}

在mountComponent中,renderWatcher是最关键的地方。通过renderWatcher,将render函数转换成vnode,然后通过vm._update,将vnode渲染成真实的dom。

2. 渲染watcher(renderWatcher)的执行过程

渲染watcher的执行过程,就是Watcher实例化的过程。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54// src/core/observer/watcher.js

export default class Watcher {

constructor (

vm: Component,

expOrFn: string | Function,

cb: Function,

options?: ?Object,

isRenderWatcher?: boolean

) {

this.vm = vm

// 当前isrenderwatcher为true

if (isRenderWatcher) {

vm._watcher = this

}

vm._watchers.push(this)

// options

if (options) {

this.before = options.before // 这个函数会选择性调用beforeUpdate钩子函数

} else {

this.deep = this.user = this.lazy = this.sync = false

}

// expOrFn 就是updateComponent函数。

if (typeof expOrFn === 'function') {

this.getter = expOrFn // 会走到这里啦

}

this.value = this.lazy // lazy是false,所以会调用this.get函数。

? undefined

: this.get()

}

get () {

pushTarget(this) // 将当前的Dep.target设置为该renderWatcher。

let value

const vm = this.vm

try {

value = this.getter.call(vm, vm) // 在这里会调用updateComponent,即vm._update(vm._render(), hydrating);

} catch (e) {

if (this.user) {

handleError(e, vm, `getter for watcher "${this.expression}"`)

} else {

throw e

}

} finally {

if (this.deep) {

traverse(value)

}

popTarget() // getter执行完毕后把Dep.target设置为之前的watcher实例。

this.cleanupDeps()

}

return value

}

}

由以上代码可知,在renderWatcher的执行过程中,this.get()的执行是最为重要的。而this.get()的执行则主要切换了Dep.target,执行了updateComponent函数。

updateComponent中, 主要执行了vm._update(vm._render(), hydrating)函数。下面着重来分析vm._update的过程。

vm._render的执行过程

_render是Vue原型上的方法,定义在src/core/instance/render.js中,下面分析下_render方法主要做了什么。

其实_render主要做了一件事,调用render函数生成vnode(虚拟dom)并返回。

1. 从vm.$options取出render和_parentVnode。在上边的例子中,new Vue({ render: h => h(App) }).$mount('#app')。

在这里, _parentVnode为undefined。

2. 设置_parentVnode为vm.$vnode,即当前vue实例的占位符vnode。

3. 设置currentRenderingInstance = vm, 同时执行渲染函数render,取得render返回的vnode。

4. 然后将currentRenderingInstance置为null

5. 返回vnode,并设置vnode.parent为_parentVnode.

在这里render函数主要调用了 render.call(vm, vm.$createElement)。对应于例子中, h就是vm.$createElement。就是根据传入的tag或者component,生成对应的vnode的一个函数。下面来分析下vm.$createElement函数。 vm.$createElement也定义在render.js文件中,在vm._init中执行挂载。vm.$createElement定义如下:

1

2

3

4

5// 最后一个true用以标示是用户主动调用,即定义了render函数。而非由编译器调用。

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

// 在例子中,传入的参数如下:

vm.$createElement(App);

createElement最终会调用_createElement,下面来分析下_createElement。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63// src/core/vdom/create-element.js

export function _createElement (

context: Component,

tag?: string | Class | Function | Object,

data?: VNodeData,

children?: any,

normalizationType?: number

): VNode | Array {

// 如果传入的data是一个响应式数据,直接创建并返回空vnode

if (isDef(data) && isDef((data: any).__ob__)) {

return createEmptyVNode()

}

// 处理动态组件情况

if (isDef(data) && isDef(data.is)) {

tag = data.is

}

if (!tag) {

return createEmptyVNode()

}

// ... 有删减

// 在例子中,这两个值是相等的。处理过程在createElement中。

if (normalizationType === ALWAYS_NORMALIZE) {

// 处理children, 最终生成一个[vnode, vnode, vnode]数组。

children = normalizeChildren(children)

} else if (normalizationType === SIMPLE_NORMALIZE) {

children = simpleNormalizeChildren(children)

}

let vnode, ns

// 此时tag为对象App

if (typeof tag === 'string') {

let Ctor

ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)

if (config.isReservedTag(tag)) {

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 {

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()

}

}

接下来会调用createComponent函数来创建组件。在createComponent中,主要做了下面几件事:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

大专栏

标签:Vue,render,dom,mount,vm,vnode,源码,._,data

来源: https://www.cnblogs.com/liuzhongrong/p/11924652.html

你可能感兴趣的:(vue的虚拟dom返回html)