Vue3——渲染函数

渲染函数

Vue.js使用了虚拟DOM机制,通过虚拟DOM来更新DOM节点,提高了渲染效率。在介绍组件时,组件模板是定义在选项对象的template选项中的,但是在一些场景中需要使用JavaScript来创建HTML,这时可以使用渲染函数(render()函数)​。

1、什么是虚拟DOM

对元素和文本的操作,实际上就是对DOM节点的操作。将元素节点添加到当前的DOM树中,或者删除DOM树中的某个元素节点,都会引起浏览器对网页的重新渲染。随着单页应用程序的广泛应用,页面跳转和更新等操作都是在同一个页面中完成的,这样就会更加频繁地操作DOM,对网页的重新渲染就会比较耗时。为了解决DOM渲染效率的问题,Vue.js采用了虚拟DOM机制。

虚拟DOM并不是真正意义上的DOM。简单来说,虚拟DOM就是用JavaScript来模拟DOM。当DOM前后发生变化时,让JavaScript来对这种变化进行比较,比较之后,只更新需要改变的DOM,不需要改变的地方不处理,用这种方法来提高渲染效率。

虚拟DOM实际上是一个JavaScript对象。和正常的DOM对象相比,JavaScript对象更简单,处理速度更快,DOM树的结构可以很容易地用JavaScript对象来表示。

例如,在HTML中创建DOM结构,代码如下:

html 复制代码
<ul id="menu">
     <li class="item">电影</li>
     <li class="item">音乐</li>
     <li class="item">读书</li>
</ul>

将上述代码中的DOM树的结构用对应的JavaScript对象来表示,代码如下:

js 复制代码
let ele = {
    tag: 'ul',
    props: {
        id: 'menu'
    },
    children: [
        {tag: 'li', props: {class: 'item'}, children: ['电影']},
        {tag: 'li', props: {class: 'item'}, children: ['音乐']},
        {tag: 'li', props: {class: 'item'}, children: ['读书']}
    ]
}

上述代码中,ele即创建的用于表示DOM树结构的JavaScript对象。使用JavaScript对象来表示DOM树的结构,当数据发生变化时直接修改这个JavaScript对象,然后将修改后和修改前的JavaScript对象进行对比,记录需要对页面执行的DOM操作,再将其应用到真正的DOM树,从而实现视图的更新。

2、 render()函数的使用

2.1、基本用法

假设要实现一个锚点的应用。锚点(anchor)是网页中超链接的一种。使用锚点可以在文档中设置标记,这些标记通常放在文档的特定主题位置或页面的顶部,然后可以创建到这些锚点的链接,这些链接可以帮助用户快速浏览指定位置。例如,生成两个带锚点的标题,代码如下:

html 复制代码
<div id="app">
    <h1>
        <a href="#music">
            音乐频道
        </a>
    </h1>
    <h2>
        <a href="#read">
            读书频道
        </a>
    </h2>
</div>
<div style="margin-top: 300px;"></div>
<a id="music">
    音乐频道
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
    读书频道
</a>
<div style="margin-top:1000px;"></div>

将这个功能封装为组件,代码如下:

html 复制代码
<div id="app">
    <anchor :level="1">
        <a href="#music">音乐频道</a>
    </anchor>
    <anchor :level="2" title="read">
        <a href="#read">读书频道</a>
    </anchor>
</div>
<div style="margin-top:300px;"></div>
<a id="music">
    音乐频道内容
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
    读书频道内容
</a>
<div style="margin-top:1000px;"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('anchor', {
        template: `
            <div>
                <h1 v-if="level === 1">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h1>
                <h2 v-if="level === 2">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h2>
                <h3 v-if="level === 3">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h3>
                <h4 v-if="level === 4">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h4> 
                <h5 v-if="level === 5">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h5>
                <h6 v-if="level === 6">
                    <a :href="'#' + title">
                        <slot></slot>
                    </a>
                </h6>
            </div>`,
        props: {
            level: {
                type: Number,
                required: true
            },
            titel: {
                type: String,
                default: ''
            }
        }
    }) 
    vm.mount('#app');
</script>

上述代码中,考虑到标题元素(

~

)可以变化,将标题的级别定义成组件的Prop,根据传递的动态属性level可以在不同级别的标题中插入锚点元素。这样写的缺点是:组件的模板代码冗长,大部分代码都是重复的。

下面使用render()函数来改写上面的代码,代码如下:

html 复制代码
<div id="app">
    <anchor :level="1">
        <a href="#music">音乐频道</a>
    </anchor>
    <anchor :level="2" title="read">
        <a href="#read">读书频道</a>
    </anchor>
</div>
<div style="margin-top:300px;"></div>
<a id="music">
    音乐频道内容
</a>
<div style="margin-top: 50px;"></div>
<a id="read">
    读书频道内容
</a>
<div style="margin-top:1000px;"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('anchor', {
        render(){
            const {h} = Vue;
            return h(
                'h' + this.level,
                {},
                this.$slots.default()
            )
        },
        props: {
            level: {
                type: Number,
                required: true
            },
            title: {
                type: String,
                default: ''
            }
        }
    }) 
    vm.mount('#app');
</script>

上述代码中,使用render()函数创建虚拟DOM,通过拼接字符串('h' + this.level)的形式来构造标题元素,将组件的子节点存储在组件实例的$slots.default()中,这样就简化了代码。

2.2、h()函数

在render()函数中,使用h()函数可以构建虚拟DOM的模板,它返回的是一个JavaScript对象。这个对象所包含的信息会告诉Vue.js页面中需要渲染什么节点,包括其子节点的描述信息。这样的节点被称为虚拟节点(virtualnode,常简写为VNode)。虚拟DOM是由Vue组件树建立起来的整个VNode树的统称。

h()函数可以接收3个参数,示例代码如下:

js 复制代码
h(
    //第一个参数,必选
    // {String | Object | Function}
    //一个HTML标签,组件选项对象,或者是一个函数
    'div',

    //第二个参数,可选
    //{ Object }
    //一个包含模板相关属性的数据对象
    {

    },

    //第三个参数,可选
    // {String | Array}
    //子虚拟节点,由h()函数构建
    //或简单的使用字符串来生成的文本节点
    [
        '文本',
        h('h1', 'Hello world'),
        h(MyComponent, {
            someProp: 'foobar'
        })
    ]
)
  • 第一个参数是必选的,它表示要创建的元素节点的名字或组件。它可以是一个HTML标签,也可以是一个组件选项对象,或者是一个函数。
  • 第二个参数是可选的,它表示元素的属性集合。它是一个包含模板相关属性的数据对象,在template中使用。
  • 第三个参数也是可选的,它表示子节点的信息。它可以是字符串文本,也可以是由h()函数构建的子虚拟节点。

在此之前,要想在模板中绑定样式或监听事件,就要在组件的标签上使用相应的指令。而在render()函数中,这些都写在包含属性的数据对象中。例如,定义一个绑定了样式且监听click事件的简单组件,当单击文本时为文本添加样式,使用template方式的代码如下:

html 复制代码
<style>
    .style{
        width: 300px;
        height: 100px;
        line-height: 100px;
        text-align: center;
        font-size: 20px;
        background: #6666FF;
        color: #FFFFFF;
    }
</style>
<div id="app">
    <ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('ele', {
        template: `
            <div id="demo"
            :class="{style:isStyle}"
            @click="addStyle">成功永远属于马上行动的人</div>`,
        data: function() {
            return {
                isStyle: false
            }
        },
        methods: {
            addStyle: function() {
                this.isStyle = true;
            }
        }
    }) 
    vm.mount('#app');
</script>

使用render()函数进行改写,代码如下:

html 复制代码
<style>
    .style{
        width: 300px;
        height: 100px;
        line-height: 100px;
        text-align: center;
        font-size: 20px;
        background: #6666FF;
        color: #FFFFFF;
    }
</style>
<div id="app">
    <ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('ele', {
        render() {
            return Vue.h(
                'div',
                {
                    class: {
                        'style': this.isStyle
                    },
                    id: 'demo',
                    onClick: this.addStyle
                },
                '成功永远属于马上行动的人'
            )
        },
        data: function() {
            return {
                isStyle: false
            }
        },
        methods: {
            addStyle: function() {
                this.isStyle = true;
            }
        }
    }) 
    vm.mount('#app');
</script>

虽然两种写法不同,但结果是相同的。由上例可知,使用template方式明显比使用render()函数方式可读性更好,代码更简洁。所以要在合适的情况下使用render()函数。

3、使用JavaScript代替模板功能

在template中可以使用Vue内置的一些指令实现某些功能,如v-if、v-for。但是在render()函数中无法使用这些指令,要实现某些功能可以使用原生JavaScript方式。

3.1、实现v-if功能

例如,在render()函数中使用原生if...else语句来判断输入的值是否为一个正整数,代码如下:

html 复制代码
<div id="app">
    请输入年龄:<input v-model="num" size="6">
    <ele :num="num"></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                num: ''
            }
        }
    });
    vm.component('ele', {
        render() {
            //判断输入是否为正整数
            if(!/^[1-9]*[1-9][0-9]*$/.test(this.num)) {
                return Vue.h(
                    'p',
                    {style: {color: 'red'}},
                    '请输入正整数!'
                )
            }else {
                return Vue.h(
                    'p',
                    {style: {color: 'green'}},
                    '输入正确!'
                )
            }
        },
        props: {
            num: {
                type: String,
                required: true
            }
        }
    }) 
    vm.mount('#app');
</script>

示例:实现文本的放大和缩小。

在render()函数中使用原生JavaScript进行判断,根据判断结果实现文本的放大和缩小。单击"放大"按钮实现文本的放大效果,单击"缩小"按钮实现文本的缩小效果。代码如下:

html 复制代码
<div id="app">
    <ele :show="show">书读百遍,其义自现。</ele>
    <button @click="show = !show">{{!show ? '放大' : '缩小'}}</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                show: false
            }
        }
    });
    vm.component('ele', {
        render() {
            if(this.show) {
                return Vue.h(
                    'p',
                    {
                        style: {color: 'blue', fontSize: '26px'}
                    },
                    this.$slots.default()
                )
            } else {
                return Vue.h(
                    'p',
                    {
                        style: {color: 'blue', fontSize: '16px'}
                    },
                    this.$slots.default()
                )
            }
        },
        props: {
            show: {
                type: Boolean,
                default: false
            }
        }
    }) 
    vm.mount('#app');
</script>

3.2、实现v-for功能

在使用v-if的模板中可以通过原生JavaScript中的if和else语句实现逻辑判断。对于v-for指令,可以使用对应的for循环来实现。例如,在render()函数中使用for语句渲染一个列表,代码如下:

html 复制代码
<div id="app">
    <ele :items="items"></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                items: ['空山不见人,', '但闻人语响。', '返景入深林,', '复照青苔上']
            }
        }
    });
    vm.component('ele', {
        render() {
            let nodes = [];
            for(let i = 0; i < this.items.length; i++) {
                nodes.push(Vue.h(
                    'p',
                    this.items[i]
                ));
            }
            return Vue.h('div', nodes);
        },
        props: {
            items: {
                type: Array
            }
        }
    }) 
    vm.mount('#app');
</script>

3.3、实现v-model功能

在render()函数中也不能使用v-model指令。如果要实现相应的功能需要自己编写业务逻辑。例如,实现与v-model指令相同的功能,将单行文本框的值和组件中的属性值进行绑定。代码如下:

html 复制代码
<div id="app">
    <ele></ele>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('ele', {
        render() {
            let self = this;
            return Vue.h(
                'div',
                [
                    Vue.h(
                        'input', 
                        {
                            value: this.value,
                            oninput: (event)=>{
                                self.value = event.target.value;
                            }
                        }
                    ),
                    Vue.h(
                        'p',
                        '输入的值:' + this.value
                    )
                ]
            )
        },
        data() {
            return {
                value: ''
            }
        }
    }) 
    vm.mount('#app');
</script>

运行上述代码,当单行文本框中的内容发生变化时,value属性值也会相应进行更新。结果如图所示。

3.4、实现事件修饰符和按键修饰符功能

对于事件处理中的事件修饰符和按键修饰符,也需要使用对应的方法来实现。常用修饰符对应的实现方法如表所示。

修饰符 对应的实现方法
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if(event.target !== event.currentTaget) return
.enter if(event.target !== event.currentTarget) return
.ctrl、.alt、.shift if(!event.ctrlKey) return
相关推荐
Ruihong2 小时前
你的 Vue KeepAlive 组件,VuReact 会编译成什么样的 React 代码?
vue.js·react.js·面试
Ruihong2 小时前
你的 Vue slot 插槽,VuReact 会编译成什么样的 React 代码?
vue.js·react.js·面试
Hello--_--World2 小时前
ES15:Object.groupBy() 和 Map.groupBy()、Promise.withResolvers() 相关知识点
开发语言·前端·javascript
一 乐2 小时前
房产租赁管理|基于springboot + vue房产租赁管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·房产租赁管理系统
Cache技术分享2 小时前
386. Java IO API - 监控目录变化
前端·后端
Hooray2 小时前
管理后台框架 AI 时代的版本答案,Fantastic-admin 6.0 它来了!
前端·前端框架·ai编程
2501_913680002 小时前
Vue3项目快速接入AI助手的终极方案 - 让你的应用智能升级
前端·vue.js·人工智能·ai·vue·开源软件
开开心心_Every2 小时前
动图制作工具,拆分转视频动态照离线免费
运维·前端·人工智能·edge·pdf·散列表·启发式算法
饭后一颗花生米2 小时前
2026 前端实战:AI 驱动下的性能优化与工程化升级
前端·人工智能·性能优化