Vue2源码梳理:render函数的实现
render
- 在 $mount 时,会调用 render 方法
- 在写 template 时,最终也会转换成 render 方法
- Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node
- 它的定义在 src/core/instance/render.js 文件中,它返回的是一个vnode
- 这里看下它的实现
Vue.prototype._render = function (): VNode { const vm: Component = this // 从 $options 中拿到这个 render 函数 // 这个 render 函数, 可以是用户自己写,也可以通过编译生成 const { render, _parentVnode } = vm.$options // 跳过 // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== 'production') { for (const key in vm.$slots) { // $flow-disable-line vm.$slots[key]._rendered = false } } // 跳过 if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject } // 跳过 // 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 { // call的第一个参数是当前上下文, vm._renderProxy 在生产环境下,就是 VM,也就是this本身, 在开发环境它可能是一个proxy对象 // 这个 vm.$createElement 也是一个函数,会在下面来说 // 总体来说就是生成 vnode vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { // 捕获并处理错误,尝试再次生成 vnode 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 !== 'production') { if (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 } } else { vnode = vm._vnode } } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { // 如果拿到的 vnode 是个数组,说明模板有多个根节点,这个在后续vue3中支持,在vue2中会报下面的警告 // 在vue2 中 vnode 是一个 vdom, if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }
- 注意,上面的 vm.$createElement 在 initRender 函数中被初始化定义
- 而 initRender 是在一开始 new Vue 的时候,会调用 _init 函数中被执行
// initRender 函数中 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
- 看到上面两个函数区别,就是最后一个参数不一样
_c
这个函数它实际上是被编译生成的render函数所使用的一个方法$createElement
是给我们手写render函数提供了一个创建vnode的一个方法var app = new Vue({ el: '#app', // 手写 render 函数 render(createElement) { return createElement('div', { attrs: { id: 'app2' // 这些写后,这里挂载的 div 元素,会替换掉之前的 #app 容器 } }, this.message) }, data() { return { message: 'Hello' } } })
- 这里,使用render和在html中写是不一样的,它没有一个把从差值变换的这个过程
- 之前的写法是在HTML里面定义了这个差值
// 之前的写法 <div id="app"> {{ message }} </div>
- 它在不执行vue的时候,它会先把这个东西渲染出来
- 然后在new vue之后, 执行 mount 的时候,再把它从差值的那个message给替换成真实的数据
- 在这里是通过纯 render 函数,当它执行完毕以后,直接挂载到页面上,这样的话体验会更好一点
- 因为这里手写了 render 函数,所以, 整个渲染流程中,就不会再执行把 template 转换 render 函数那一步了
- 所以就相当于直接就达到了一样的效果
vm._renderProxy
的定义,也是发生在 src/core/instance/init.js 中- 在 _init 函数中,有一个判断
/* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm }
- 当前如果说是生产环境,
vm._renderProxy = vm
- 开发阶段开发阶段,则执行
initProxy(vm)
定义在 src/core/instance/proxy.js 中// 报错提示函数 const warnNonPresent = (target, key) => { warn( `Property or method "${key}" is not defined on the instance but ` + 'referenced during render. Make sure that this property is reactive, ' + 'either in the data option, or for class-based components, by ' + 'initializing the property. ' + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', target ) } // const hasHandler = { has (target, key) { const has = key in target const isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)) // 全局方法或者私有方法,或不在$data中 // 属性不在 target 下,并且不被允许,则报错提示,比如:使用了 一个没有在 data 中定义的变量,就会报错 if (!has && !isAllowed) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return has || !isAllowed } } // 判断当前浏览器是否支持 proxy,Proxy作用是对对象的访问做一个劫持 const hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy) initProxy = function initProxy (vm) { if (hasProxy) { // determine which proxy handler to use const options = vm.$options const handlers = options.render && options.render._withStripped ? getHandler : hasHandler vm._renderProxy = new Proxy(vm, handlers) // 在这里 handlers 是 hasHandler } else { vm._renderProxy = vm } }
- 这里可以看到,支持 Proxy 则
vm._renderProxy = new Proxy(vm, handlers)
- 当前如果说是生产环境,
- 通过以上分析可知,vm._render 最终是通过执行 createElement 方法并返回的是 vnode
- 而返回的vnode 它是一个虚拟 Node