当前位置: 首页 > news >正文

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

相关文章:

  • iTop-4412 裸机程序(二十二)- RTC时钟
  • c++恶魔轮盘制造第1期输赢
  • 第62讲商品搜索动态实现以及性能优化
  • CTFshow web(php命令执行 55-59)
  • 容器高级知识: 适配器模式与 Sidecar 模式的区别
  • Python 读取pdf文件
  • 如何流畅进入Github
  • Spring + Tomcat项目中nacos配置中文乱码问题解决
  • HarmonyOS 鸿蒙 ArkTS ArkUI 页面之间切换转换动画设置
  • 微服务OAuth 2.1认证授权可行性方案(Spring Security 6)
  • [嵌入式系统-14]:常见实时嵌入式操作系统比较:RT-Thread、uC/OS-II和FreeRTOS、Linux
  • uniapp的配置和使用
  • flask+python儿童福利院管理系统pycharm毕业设计项目
  • C++ 关键字小结
  • 使用 Elasticsearch 和 OpenAI 构建生成式 AI 应用程序
  • Java+SpringBoot构建智能捐赠管理平台
  • re:从0开始的CSS之旅 13. 文档流
  • 第十九篇【传奇开心果系列】Python的OpenCV库技术点案例示例:文字识别与OCR
  • 服务器安装Docker (centOS)
  • 《走进科学》灵异事件:Nginx配置改了之后一直报错
  • 首部关于民营经济发展的基础性法律,有何亮点?专家解读
  • 王受文已任中华全国工商业联合会领导班子成员
  • 南京106亿元成交19宗涉宅地块:建邺区地块楼面单价重回4.5万元
  • 新型算法助力听障人士听得更清晰
  • 何立峰出席驻沪中央金融机构支持上海建设国际金融中心座谈会并讲话
  • 成都世运会倒计时100天,中国代表团运动员规模将创新高