阅读vue源码笔记(五)_render

2020-03-20  本文已影响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的实例,如果不是则说明当前存在多个根节点。比如说我们在组件里写了两个根节点,就会看到这个错误。

上一篇下一篇

猜你喜欢

热点阅读