Vue源码分析——虚拟dom如何渲染成真实dom
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Vue源码分析——虚拟dom如何渲染成真实dom ,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含9056字,纯文字阅读大概需要13分钟 。
内容图文
今天我们来说下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 74 import Vue from "vue"; import App from "./App.vue"; Vue.config.productionTip = false; new Vue({ render: h => h(App) }).$mount("#app"); // app.vue <template> <div id="app"> <img width="25%" src="./assets/logo.png"> <HelloWorld msg="Hello Vue in CodeSandbox!" /> </div> </template> <script> import HelloWorld from "./components/HelloWorld"; export default { name: "App", components: { HelloWorld } }; </script> <style> #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; } </style> // HelloWorld.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <h3>Installed CLI Plugins</h3> </div> </template> <script> export default { name: "HelloWorld", props: { msg: String } }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
$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<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { // 如果传入的data是一个响应式数据,直接创建并返回空vnode if (isDef(data) && isDef((data: any).__ob__)) { return createEmptyVNode() } // 处理<component is=""></component>动态组件情况 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源码分析——虚拟dom如何渲染成真实dom 全部内容,希望文章能够帮你解决Vue源码分析——虚拟dom如何渲染成真实dom 所遇到的程序开发问题。
如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
来源:【匿名】