• 猿创征文|Vue源码分析(Render渲染函数)


    Render函数

    渲染DOM原理

    在前面的内容我们学习到了Vue的响应式,但是只有响应式是不够的,我们要将内容渲染出来。Vue的templates就是通过Render(渲染)函数渲染出来的。

    第一阶段(生成虚拟DOM)

    在Vue中,如果你把模版直接传入Vue实例,那Vue会执行完整的编译,把传入的template编译为浏览器可运行的DOM,就像你直接在DOM中编写模板一样。

    如果你使用vue-cli构建项目,会用到webpack和vue-loader。它会在构建时预编译模板为可以直接解析的DOM代码(h()函数),即纯JavaScript代码。还有另一种编译模式,就是将编译器也打包进去,压缩之后会比第一种体积大一些。

    上面两种情况其实都是使用Render函数生成虚拟DOM

    第二阶段(生成真实DOM)

    Vue会基于第一阶段的虚拟DOM转换成真实DOM。

    虚拟DOM更新

    第一、二阶段相当于完成了初始化,如果之后DOM需要更新呢?回顾之前讲的autorun函数(用于订阅发布),我们可以将虚拟DOM的代码放在里面,当数据改变时,Render函数会生成新的虚拟DOM,即触发我们的发布,新的虚拟DOM和旧的虚拟DOM进行比较,得出最少需要更新的节点并生成真实DOM完成一次更新。

    虚拟DOM

    简单来说,就是将真实的DOM转化成JS代码,既然都是JS代码,虚拟DOM产生变化的时候也只需要修改JS的一些内容,最终再转换成真实DOM。

    你可能会好奇,我本来直接操作DOM就可以,为什么要在中间加一系列操作呢?

    这是由于操作真实DOM中会存在一些问题,当DOM节点非常多的时候:会有资源消耗问题和执行效率问题。

    使用JavaScript操作真实DOM会非常消耗资源,因为要修改真实DOM操作的内容很多,但是如果使用虚拟DOM,你无论是如何增删修改节点,都只是在操作JS,这样会很节省资源。同样,这样只操作JS,用JS计算差异,也会比真实DOM比较差异快很多。(当然:如果你能将DOM操作到炉火纯青,保证每次对DOM的操作都是较为节约快捷的方式,那还是要比虚拟DOM快的,毕竟我们多了一步将它转换成了JS,又变回真实DOM的过程)

    Hello World
    • 1

    调用Render函数转化为虚拟DOM

    import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, "Hello World"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    Vue整体机制

    现在我们来整合一下之前的所有知识

    每个组件都有一个渲染函数,它包装在我们第一节实现的autorun函数中,当执行渲染的时候,getters收集它的依赖。同时,每个组件都有一个watcher观察者去监听,一旦数据更新或依赖的渲染属性发生变化,我们就执行收集的依赖(即渲染函数),这样每个组件即可实现自己的自动循环渲染。

    请添加图片描述

    Render Function API
    export default{
    	render(h){
    		return h('div',{},[...])
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面是渲染函数的使用,你需要一个参数h,h是一种简写表示超脚本(HyperScript),这是一种虚拟DOM渲染函数的编写风格。就像超文本叫HTML一样,他没有什么特殊的含义,只是方便书写的表现形式而已。

    接下来我们分析一下h()

    h()接收三个函数,第一个是元素类型;第二个是参数对象();第三个是子节点

    h('div','some text')
    h('div',{class:'foo'},'some text')
    h('div',{...},[
    	'some text',
    	h('span','bar')
    ])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后一个的渲染效果就像

    some text bar
    • 1

    h()同样也接受一个组件定义,这将会创建一个组件实例

    import MyComponent from '...'
    h(MyComponent,{
    	props:{...}
    })
    
    • 1
    • 2
    • 3
    • 4
    练习Render函数动态渲染标签

    前面说了这么多,让我们来做一个demo真正体验一下

    传入tags数组

    
    
    • 1

    渲染出标签并显示元素索引值

    0

    1

    2

    • 1
    • 2
    • 3
    • 4
    • 5

    如果经常使用Vue,你的第一反应可能是使用一个构造器,动态渲染

    
    
    • 1

    但是这个组件设计的初衷是用来在组件间动态切换,而不是渲染真实的标签;如果Vue没有将这个api提供给我们,我们的开发就会受限。这也是模板的一个缺点,不如jsx灵活。开发者总不能等待vue官方提供解决办法,所以在这里我们使用渲染函数

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们通过example创建的模板就是这样

    0

    1

    2

    • 1
    • 2
    • 3
    • 4
    • 5
    函数组件和状态组件

    上面我们使用的是Vue的状态组件,而函数组件不包含state和props,你可以理解它就是一个函数。

    const foo = {
    	functional: true,
    	render: h => h('div','foo')
    }
    
    • 1
    • 2
    • 3
    • 4

    函数组件特点:

    1. 组件不支持实例化。
    2. 优化更优,因为在Vue中它的渲染函数比父级组件更早被调用,但是他并不会占用很多资源,因为它没有保存数据和属性,所以它常用于优化一个有很多节点的组件。
    3. 容易扩展,如果你的组件只是用来接收 prop然后显示数据,或者一个没有状态的按钮,建议使用函数组件。
    4. 函数组件没有this,获取prop可以通过render函数的第二参数得到render(h, context)

    我们改写一下上面的demo

    Vue.component('example', {
      functional: true,
      props: {
        tags: {
          type: Array,
          validator (arr) { return !!arr.length }
        }
      },
      render: (h, context) => {
        const tags = context.props.tags
        return h('div', context.data, tags.map((tag, index) => h(tag, index)))
      }
    })
    
    new Vue({ el: '#app' })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    练习Render函数动态渲染组件

    渲染函数除了可以渲染普通标签外,还可以渲染组件,下面代码有FooBar组件,点击toggle按钮的时候,切换两组件的显示状态。

    
    
    
    • 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
  • 相关阅读:
    【matplotlib 实战】--平行坐标系
    LeetCode //C - 67. Add Binary
    Linux——DNS(正向解析+反向解析+多域配置+主从配置)
    嵌入式实时操作系统的设计与开发(概述学习)
    【机器学习技巧】回归模型的几个常用评估指标(R2、Adjusted-R2、MSE、RMSE、MAE、MAPE)及其在sklearn中的调用方式
    Opencv项目实战:08 Yolov3更高精度的检测物体
    linux常见命令(七)
    【算法可视化】模拟算法专题
    Win10 环境下 VS2022 暴力编译PP-OCRv4
    java个人资金账户管理计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  • 原文地址:https://blog.csdn.net/qq_47234456/article/details/126792410