• render


    关键词:vm.$createElementvm._renderProxyVNode

    逻辑:vm._render最终是通过执行createElement方法并返回的是vnode

    src/core/instance/lifecycle.js文件运行的vm._render()定义在src/core/instance/render.js文件中。

    src/core/instance/render.js文件解析

    vm.$createElement

    (1)定义了原型上_render的私有方法,返回的是一个VNode。从$options拿到render函数(render函数可以用户自己写也可以通过编译生成),利用render.call()方法传入vm._renderProxyvm.$createElementcall的第一个参数是当前上下文,vm._renderProxy在生产环境( production )下就是vm,在开发环境( development )中可能是proxy对象。vm.$createElementinitRender函数中定义。

    initRender函数在src/core/instance/init.js中执行initRender(vm)initRender中定义了两个函数:vm._cvm.$createElement。这两个函数最终都调用了createElement方法,区别是最后一个参数不一样。因为vm._c是被编译生成的render函数所使用的方法,vm.$createElement实际上是给我们手写render函数提供了一个创建 VNode 的方法。

    // src/core/instance/render.js
    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)
      }
    }
    
    • 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
    // 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
    }
    
    • 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

    (2)手写render函数(使用$createElement

    这跟直接在 html 上写是不一样的。他没有一个把从插值({{message}})变换的过程。之前的写法是在 html 里面定义了一个插值,会先把插值渲染出来,然后在new Vue之后执行$mount的时候再把插值替换成真实的数据。

    而这是通过纯render函数,不用在页面上显示插值,而是通过render函数执行完毕之后把message替换上去,体验会更好。因为手写render函数就不会执行把template转换成render函数这一步了,挂载的元素(id="app1")实际上会替换掉定义的(id="app")。这就是为什么不能在body上做这个事情,因为会把body替换。

    <div id="app">div>
    
    • 1
    export default {
      data() {
        return {
          message: 'Hello Vue!',
        };
      },
      // render第一个参数是 vm.$createElement ,所以createElement是函数创建 VNode , VNode标签 是个 div
      render(createElement){
        return createElement('div',{
          attrs:{
            id:'app1'
          }
        },this.message)
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    vm._renderProxy

    (1)vm._renderProxy也是发生在src/core/instance/init.js中,如果当前是生产环境,就vm._renderProxy = vm,开发阶段则initProxy(vm)initProxy()的定义在src/core/instance/proxy.js文件中。

    判断hasProxy(判断当前浏览器支不支持proxyproxy实际上是 ES6 提供的 API(阮一峰老师) ,实际作用就是对对象访问做一个劫持)。因为chrome支持proxy,所以会执行vm._renderProxy = new Proxy(vm, handlers)handlers在目前条件下指向hasHandlerhasHandler是个判断,如果我们的元素不在target下,hasfalseisAllowed是全局的属性和方法。在两个情况都不满足的条件下,执行warnNonPresent方法。warnNonPresent就是报警告。该警告是报使用了一个未在 data、method 定义的一个变量。

    let initProxy
    
    if (process.env.NODE_ENV !== 'production') {
      const allowedGlobals = makeMap(
        'Infinity,undefined,NaN,isFinite,isNaN,' +
        'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
        'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' +
        'require' // for Webpack/Browserify
      )
    
      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 warnReservedPrefix = (target, key) => {
        warn(
          `Property "${key}" must be accessed with "$data.${key}" because ` +
          'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
          'prevent conflicts with Vue internals. ' +
          'See: https://vuejs.org/v2/api/#data',
          target
        )
      }
    
      const hasProxy =
        typeof Proxy !== 'undefined' && isNative(Proxy)
    
      if (hasProxy) {
        const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
        config.keyCodes = new Proxy(config.keyCodes, {
          set (target, key, value) {
            if (isBuiltInModifier(key)) {
              warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
              return false
            } else {
              target[key] = value
              return true
            }
          }
        })
      }
    
      const hasHandler = {
        has (target, key) {
          const has = key in target
          const isAllowed = allowedGlobals(key) ||
            (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
          if (!has && !isAllowed) {
            if (key in target.$data) warnReservedPrefix(target, key)
            else warnNonPresent(target, key)
          }
          return has || !isAllowed
        }
      }
    
      const getHandler = {
        get (target, key) {
          if (typeof key === 'string' && !(key in target)) {
            if (key in target.$data) warnReservedPrefix(target, key)
            else warnNonPresent(target, key)
          }
          return target[key]
        }
      }
    
      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
        }
      }
    }
    
    export { initProxy }
    
    • 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
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    render方法实际上就是生成一个vnode,出错的话handleError会给用户一个接口去处理一些错误,再做一系列降级。再对vnode进行判断是不是VNode,如果同时是个Array,说明模板会有多个根节点,会返回多个vnodevnode实际上是Virtual DOM的概念。

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
    
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
    
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
    
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }
    
    • 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
  • 相关阅读:
    MindSpore:model.train中的dataset_sink_mode该如何理解?
    代码随想录1刷—链表篇
    Deploy、Service与Ingress
    leetcode39. 组合总和
    .net 微服务 服务保护 自动重试 Polly
    【LeetCode】658. 找到 K 个最接近的元素
    VUE之旅—day1
    Sermant运行流程学习笔记,速来抄作业
    Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装
    java的springboot框架中使用logback日志框架使用RabbitHandler注解为什么获取不到消费的traceId信息?
  • 原文地址:https://blog.csdn.net/qq_44997147/article/details/126212577