vue中render函数的作用,以及什么是虚拟DOM?

1,770 阅读2分钟

前言

本文详细的介绍了render是什么?render的作用是什么?以及虚拟DOM的好处是什么?还有些小demo可以测试一下

你知道的越多,你不知道的越多 点赞再看,手留余香,与有荣焉

render()函数

什么是render?

简单粗暴的理解: render的作用就是来渲染虚拟DOM的

main.js

import Vue from 'vue'
import App from './App.vue'
new Vue({
    render: h=>h(App)
}).$mount('#app')

render是一个配置项,它的作用是指定渲染函数,这里采用是的箭头函数的简写格式,完整的写法如下:

render: function(h) { return h(APP) }
  • 上面的h只是一个形参, 在执行reader时会自动传入实参。

  • $mount的作用是Vue实例挂载在<div id="app">的元素中

  • $mount的作用是把前面产生的vue实例挂载到一个id名为#app的dom元素上。

  • 在本项目,这个dom就是public/index.html中的#app

  • 下面两种写法是等价: new Vue({}).$mount('#app') <====> new Vue({el: '#app})

认识render

查看自动生成的render

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <ul><li>123</li></ul>
    <p>{{msg}}</p>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'vue'
      }
    })

    // 上面的代码中,我们并没有指定渲染函数,但是,vue会自动 把el解析成渲染函数
    console.log(vm.$options.render)

    // with(this): 绑定this
    // _C :是一个函数。用来创建虚拟dom.
    // (function anonymous() {
    //   with(this){
    //     return _c('div',{
    //                       attrs:{
    //                         "id":"app"
    //                       }
    //                     },
    //                     [
    //                       _c('p', [ _v(_s(msg))] ) 
    //                     ]
    //                   )
    //   }
    // })

  </script>

</body>
</html>

基本格式

在组件中,它就是一个配置项,与data,methods,computed,created一样。

{
    data(){},
    methods:{},
    // h 是一个形参
    render(h){
        return h(参数1,参数2,参数3);
        // 参数1: String | Object | Function
        //        是一个html标签,或者是一个组件,或者是一个函数 

        // 参数2:Object。是对参数1所表示的对象的设置。

        // 参数3:参数1表示的对象的子对象。
    }
}

形参

在render()被调用时,vue会自动传入一个实参 给h。这个实参是一个函数(createElement),在组件内部,我们也可以通过this.$createElement来获取它。


Vue.component("com2",{
    data(){
        return {title:"com1"}
    },
    render(h){
        console.log(this.$createElement === h); //true
        return h('h2',[`vue-`,this.title])
    },
    mounted(){
        console.log(this.$createElement)
    }
})

返回值

render()方法使用传入的h函数来创建一个东东,然后再返回出去,那么,这个东东是什么?为什么它能起到和template一样的效果?

结论是:vnode,也就是我们说的虚拟dom.

image.png

它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

宏观理解Vue的工作过程

把模板解析成AST(抽象语法树);

AST编译成渲染函数

渲染函数结合数据生成虚拟Dom树

image.png

Vue的应用程序的主要工作步骤:

  • 模板通过编译生成AST

  • 由AST生成Vue的render函数(渲染函数)

  • 渲染函数结合数据生成Virtual DOM树,Diff和Patch后生成新的UI。

  • 模板:Vue的模板基于纯HTML,基于Vue的模板语法,我们可以比较方便地声明数据和UI的关系(template标签下的内容;el中指定的内容)。

  • AST:AST是Abstract Syntax Tree的简称(抽象语法树),Vue使用HTML的Parser将HTML模板解析为AST,并且对AST进行一些优化的标记处理,提取最大的静态树,方便Virtual DOM时直接跳过Diff。

  • 渲染函数:渲染函数是用来生成Virtual DOM的。Vue推荐使用模板来构建我们的应用界面,在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制 (这部分是我们今天主要要了解和学习的部分)。

    • 方法一: vue通过你提供的template来自动生成
    • 方法二: 直接指定render配置项。
  • Virtual DOM:虚拟DOM就是使用js的object模拟真实的dom,当状态(data)发生变化,更新之前做diff(对比新旧虚拟dom),达到最少操作dom的效果。Vue的Virtual DOM Patching算法是基于 Snabbdom 的实现,并在些基础上作了很多的调整和改进。

<!-- 真实dom -->
<div id="app">
    <p>节点1</p>
</div>

<script>
    <!--虚拟dom -->
    const vnode = {
        tag: 'div',
        data: {id: 'app'},
        children: {
            tag: 'p',
            data: {},
            children: '节点1'
        }
    } 
</script>  
  • Watcher:每个Vue组件都有一个对应的watcher,这个watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染。
  1. template,el的作用是用来生成渲染函数。
  2. 用 渲染函数 + 当前数据 ====> 虚拟DOM
  3. 虚拟DOM ===>真实的dom

通过 render函数把整个过程分成两部分:

  • 编译期: 将Vue的模板转换为渲染函数

image.png

  • 运行时:主要是基于渲染函数生成Virtual DOM树,并实时观测数据的变化,一旦数据变化了,重新调渲染函数生成新的虚拟DOM,并与旧虚拟DOM树相比,找出其中的区别,更新到真实DOM上。

image.png

如果直接指定render函数,就省略了模板的编译过程,vue运行的更快

定义渲染函数的三种方式

渲染函数(数据) ====》 视图

  • el: (间接定义)在定义vue实例时使用,vue会解析其中的内容生成渲染函数。定义组件时不能使用。
  • template:(间接定义) 在定义组件时使用。vue会解析其中的内容生成渲染函数。也是官方推荐的方式。
  • 直接写render。特殊情况下,灵活使用,难度较大。 例子:
<div id="app">
        <div>{{msg}}</div>
    </div>

    <script type="text/javascript">
        var vm = new Vue({
            data:{ msg: 'vue' },
            el: "#app",
            template: `<div>template</div>`,
            render (h) {
                return h("div", 'render')
            }
        })
        // 可以通过如下代码来查看当前vue实例的render
        console.log( vm.$options.render )
    </script>

上面通过el,template,或者render其实都会指定渲染函数,前面两个是vue自动解析的,render配置是给我们一个机会:直接自已定制render函数。

图示:

image.png

特别地,在单独使用template和render时(不写el),要通过$mount()把vue实例挂载到指定的dom

var vm = new Vue({
     data:{ msg: 'vue' },
      render (h) {
         return h("div", 'render')
      }
})
vm.$mount('#app')

这三种方法的优先级是:

render > tempalte > el

观察整个Vue的工作过程

image.png

示例代码

<div id="app">
        <p v-for="item in list">{{item}}</p>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data: {
                list: ['html', 'css', 'js']
            }
        })
    </script>
  • 模板

image.png

  • 渲染函数

image.png

  • 数据

image.png

  • 真实DOM

image.png

什么是虚拟DOM?

就是js中的对象来模拟真实的DOM,当状态(data)发生变化,想要更快的知道哪些DOM节点发生变化,通过虚拟DOM检测哪些发生变化,再更新到真实的DOM上,可以理解为就是一个js对象

虚拟DOM的好处?

  1. 我们知道访问DOM是非常昂贵的,会造成相当多得性能浪费,所以我们试想,当某个状态发生变化时,只更新与这个状态相关联的DOM节点
  2. 虚拟DOM可以跨平台

createElement

render()是依赖于createElement来创建虚拟dom的,所以,我们要来具体学习它的用法。

功能

创建虚拟dom(VNode)。

格式

createElement h(参数1,参数2,参数3);
// 参数1: String | Object | Function
//        是一个html标签,或者是一个组件,或者是一个函数 

// 参数2:Object。是对参数1所表示的对象的设置。

// 参数3:参数1表示的对象的子对象。理解为html标签中的子标记(如:ul下的li)

参数1

取值有三种可能:{String | Object | Function}

第一个参数对于createElement而言是一个必须的参数,这个参数可以是字符串string、是一个对象object,也可以是一个函数function

下面是对应的示例:

字符串

取值有三种可能:{String | Object | Function}

第一个参数对于createElement而言是一个必须的参数,这个参数可以是字符串string、是一个对象object,也可以是一个函数function

下面是对应的示例:

Vue.component('custom-element', {
    render(h) {
        return h('div')
    }
})

产生一个空标签<div></div>

对象 (就是一个组件对象) , main.js中就是这种写法

Vue.component('custom-element', {
    render(h){
        return h({
            template: `<h2>{{title}}</h2>`,
            data(){return{title:"vue"}}
        })
    }
})

得到的真实dom:<h2>vue</h2>

函数

在这个函数中返回一个组件。

Vue.component('custom-element', {
    render: function (createElement) {
        var eleFun = function () {
            return {
                template: `<div>Hello Vue!</div>`
            }
        }
        return createElement(eleFun())
	}
})

参数2

参数2是一个对象,它用来表示对参数1的描述。它是可选的。

{
    class:{ className1 : true, className2: true},
    style:{
        color: 'red',
        fontSize:'20px'
        ....
    },
    // 正常的HTML特性
    attrs:{
        id:"yourId"
    },
    //  组件的props
    props:{
        
    },
    //  DOM属性
    domProps:{
        innerHTML: "baz"
    },
    // 事件,this.$emit
    on:{
        click: this.hClick,
        customEvent:this.hCustomEvent,
        ......
    },
    // 原生事件
    nativeOn:{
        click: this.nativeClickHandler
    },
    directives:[{
      name:"",
      value:"",
      expression: "1 + 1",
      arg:'foo',
      modifiers:{bar:true}
    }]
}

参数3

格式是字符串或数组。用来表示子内容。

理解为ul中的li:ul是父级节点,li是子级节点

它有两种格式:

  • 字符串。它最终会渲染成元素的内容。
  • 数组。它最终会在为元素的子节点
render(h){
    return h("div",{class:"a"},[h('span','span的内容'),h('p','p的内容')])
},

image.png

对应生成的dom结构是:

<div class="a"><span>span的内容</span><p>p的内容</p></div>

ps:

如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请著明出处,如果有问题也欢迎私信交流