美文网首页
阅读vue源码笔记(五)_render

阅读vue源码笔记(五)_render

作者: 景阳冈大虫在此 | 来源:发表于2020-03-20 18:47 被阅读0次

    上篇我们说到mount方法里面有个关键方法mountComponent,里面的渲染watcher执行的updateComponent代码如下:

     updateComponent = () => {
          vm._update(vm._render(), hydrating)
     }
    

    本次就来说说这个_render做了什么

    前提知识

    render:字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。

    渲染函数 render

    Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

     let p = new Proxy(target, handler);
    

    Proxy简单说来就是利用handler做对target各种操作的代理,更具体的可以自行去MDN了解一下。

    _render

    调试代码

    import Vue from 'vue';
    import App from './App.vue';
    
    Vue.config.productionTip = false;
    
    var app = new Vue({
        el: '#app',
        components: {
            App,
        },
        render: h => h(App),
        data() {
            return {
                textHi: 'hi',
            };
        },
    });
    
    // src/core/instance/render.js
      Vue.prototype._render = function (): VNode {
        const vm: Component = this
        const { render, _parentVnode } = vm.$options
    
        if (_parentVnode) {
          vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
          )
        }
    
        // 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 {
          // There's no need to maintain a stack because all render fns are called
          // separately from one another. Nested component's render fns are called
          // when parent component is patched.
          currentRenderingInstance = vm
          vnode = render.call(vm._renderProxy, vm.$createElement)
        } catch (e) {
          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' && 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
          }
        } finally {
          currentRenderingInstance = null
        }
        // if the returned array contains only a single node, allow it
        if (Array.isArray(vnode) && vnode.length === 1) {
          vnode = vnode[0]
        }
        // return empty vnode in case the render function errored out
        if (!(vnode instanceof VNode)) {
          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
      }
    

    这个_render函数执行中,通过

    vnode = render.call(vm._renderProxy, vm.$createElement)
    

    得到一个vnode对象,这里的render内容如下图:


    render

    render函数可以通过用户定义,也可以通过编译生成

    initRender&createElement

    这个_c是在initRender里被定义的

    export function initRender (vm: Component) {
      vm._vnode = null // the root of the child tree
      vm._staticTrees = null // v-once cached trees
      const options = vm.$options
      const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
      const renderContext = parentVnode && parentVnode.context
      vm.$slots = resolveSlots(options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
      // bind the createElement fn to this instance
      // so that we get proper render context inside it.
      // args order: tag, data, children, normalizationType, alwaysNormalize
      // internal version is used by render functions compiled from templates
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
      // normalization is always applied for the public version, used in
      // user-written render functions.
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
    
      // $attrs & $listeners are exposed for easier HOC creation.
      // they need to be reactive so that HOCs using them are always updated
      const parentData = parentVnode && parentVnode.data
    
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
          !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
        }, true)
        defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
          !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
        }, true)
      } else {
        defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
        defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
      }
    }
    

    我们可以看initRender里定义了vm._cvm.$createElement,他们其实作用都是调用createElement生成,不一样的是_c是编译生成的render函数里用的,而$createElement为用户设置的render函数里用的。
    至于为什么render函数内部是这样的可以参考createElement参数

    createElement参数

    _renderProxy

    那么vm._renderProxy是什么呢?
    在initProxy里定义了实例的_renderProxy ,在这里对vm的操作做了劫持,如果没有在方法或者data里定义了要访问的值,则会调用warn在控制台会出现警告

    // src/core/instance/proxy.js
    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)
        } else {
          vm._renderProxy = vm
        }
      }
    

    回到_render

    ...
     if (Array.isArray(vnode) && vnode.length === 1) {
          vnode = vnode[0]
        }
        // return empty vnode in case the render function errored out
        if (!(vnode instanceof VNode)) {
          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()
        }
    ...
    

    接下来判断了一次得到的vnode是否为数组,如果是则取数组里第一个
    再判断vnode是否为VNode的实例,如果不是则说明当前存在多个根节点。比如说我们在组件里写了两个根节点,就会看到这个错误。

    相关文章

      网友评论

          本文标题:阅读vue源码笔记(五)_render

          本文链接:https://www.haomeiwen.com/subject/tvsfyhtx.html