vue如何通过VNode渲染节点

简介: vue如何通过VNode渲染节点


vue的源码包含三大核心

Compiler模块:编译模板系统

Runtime模块:也可以称之为Renderer模块,真正的渲染的模块

Reactivity模块:响应式系统

三个系统之间如何协同工作呢?

实现一个Mini-Vue

包含三个模块:

渲染系统模块

可响应式系统模块

应用程序入口模块

渲染系统的实现

该模块主要包含三个功能:

功能一:h函数,用于返回一个VNode对象;

功能二:mount函数,用于将VNode挂载到DOM

功能三:patch函数,用于对两个VNode进行对比,决定如何处理新的VNode

第一步,创建一个renderer.js文件,定义一个h函数

const h = (tag, props, children) => {
    // vnode就是一个JavaScript对象
    return {
        tag,
        props,
        children
    }
}

在html文件中,引入文件,并创建一个虚拟节点,可以输出打印一下这个vnode

<div id="app"></div>
        <script src="./renderer.js"></script>
        <script>
            // 1、 通过h函数来创建一个vnode
            const vnode = h('div', {
                class: 'vnode'
            }, [
                h('h2', null, '当前计数:100'),
                h('button', null, "+1")
            ])
            console.log(vnode)
        </script>

第二步,实现挂载功能

在renderer.js文件中定义mount方法

const mount = (vnode, container) => {
    //1、 将vnode变为elemnt,创建出真实的dom,并且在vnode上保存一份el
    const el = vnode.el = document.createElement(vnode.tag)
    //    2、处理props
    if (vnode.props) {
        for (const key in vnode.props) {
            const value = vnode.props[key]
            // 判断传递过来的是否是方法,比如onClick
            if (key.startsWith("on")) {
                el.addEventListener(key.slice(2).toLowerCase(), value)
            }
            // 设置属性
            el.setAttribute(key, value)
        }
    }
    // 3、处理children
    if (vnode.children) {
        // 如果子节点存在并且子节点是字符串,说明是其中的内容
        if (typeof vnode.children === 'string') {
            // 将内容放进去
            el.textContent = vnode.children
        } else {
            // 说明子节点中是一个数组,其内部还有子节点
            vnode.children.forEach((item) => {
                // 再次调用挂载到el上
                mount(item,el)
            })
        }
    }
    // 4、将el挂载到container上
    container.appendChild(el)
}

在html文件中调用该mount方法

// 2、通过mount函数,将vnode挂载到#app上
mount(vnode,document.getElementById('app'))

再次刷新页面的时候就可以看到界面已经加载出来了vnode

第三步实现diff算法

第一种情况:节点不相同

新建一个vnode

// 3、创建一个新的vnode
            const vnode1 = h('h2', {
                class: 'vnode'
            }, 'jerry')

将新的vnode替换旧的vnode,两个vnode之间进行一个diff算法,根据diff算法找到需要修改真实dom的那个地方,找到之后在进行修改

在renderer.js文件中定义一个patch方法

const patch=(n1,n2)=>{
    // 判断两个vnode的类型是否一样,比如说n1为div,n2为h2
    if(n1.tag!==n2.tag){
        // 拿到n1节点的父元素
        const n1ElementParent=n1.el.parentElement;
        // 移除n1节点
        n1ElementParent.removeChild(n1.el)
        // 将n2节点添加上去
        mount(n2,n1ElementParent)
    }else{
    }
}

在html文件中使用patch方法

patch(vnode,vnode1)

再次刷新页面可以看到已经替换

第二种情况:节点相同,类名不同

patch方法

const patch = (n1, n2) => {
    // 判断两个vnode的类型是否一样,比如说n1为div,n2为h2
    if (n1.tag !== n2.tag) {
        // 拿到n1节点的父元素
        const n1ElementParent = n1.el.parentElement;
        // 移除n1节点
        n1ElementParent.removeChild(n1.el)
        // 将n2节点添加上去
        mount(n2, n1ElementParent)
    } else {
        // 1、拿出element对象,并在n2中保留一份
        const el = n2.el = n1.el
        // 2、处理props
        const oldProps = n1.props || {}
        const newProps = n2.props || {}
        // 2、1获取所有的newProps添加到el中
        for (const key in newProps) {
            const oldValue = oldProps[key]
            const newValue = newProps[key]
            if (newValue !== oldValue) {
                // 判断传递过来的是否是方法,比如onClick
                if (key.startsWith("on")) {
                    el.addEventListener(key.slice(2).toLowerCase(), newValue)
                } else {
                    el.setAttribute(key, newValue)
                }
            }
        }
        // 2、2删除旧的props
        for(const key in oldProps){
            if(!(key in  newProps)){
                if (key.startsWith("on")) {
                    const value=oldProps[key]
                    el.removeEventListener(key.slice(2).toLowerCase(), value)
                } else {
                    el.removeAttribute(key)
                }
            }
        }
        // 3、处理children
    }
}

在html中新建一个节点,调用patch方法

// 3、创建一个新的vnode
            const vnode1 = h('div', {
                class: 'jerry'
            }, 'jerry')
            patch(vnode,vnode1)

之前

更新之后

接下来处理子节点

// 3、处理children
        const oldChildren = n1.children || [];
        const newChildren = n2.children || [];
        // 情况一:newChildren是一个string类型
        if (typeof newChildren === "string") {
            if (typeof oldChildren === "string") {
                if (newChildren !== oldChildren) {
                    el.textContent = newChildren
                }
            } else {
                el.innerHTML = newChildren;
            }
        }else{
            // 情况二:newChildren是一个数组
            if(typeof oldChildren==='string'){
                el.innerHTML=""
                newChildren.forEach(item=>{
                    mount(item,el)
                })
            }else{
                // oldChildren:[n1,n2,n3]
                // newChildren:[n1,n2,n3,n4,n5]
                // 前面有相同节点的元素进行patch操作
                const commonLength=Math.min(oldChildren.length,newChildren.length)
                for(let i=0;i<commonLength;i++){
                    patch(oldChildren[i],newChildren[i])
                }
                // 如果newChildren.length>oldChildren
                // oldChildren:[n1,n2,n3]
                // newChildren:[n1,n2,n3,n4,n5]
                if(newChildren.length>oldChildren.length){
                    newChildren.slice(oldChildren.length).forEach(item=>{
                        mount(item,el)
                    })
                }
                // 如果newChildren.length<oldChildren
                // oldChildren:[n1,n2,n3,n4,n5]
                // newChildren:[n1,n2,n3]
                if(newChildren.length<oldChildren.length){
                   oldChildren.slice(newChildren.length).forEach(item=>{
                       el.removeChild(item.el)
                   })
                }
            }
        }
    }

创建两个不同的节点,在进行patch操作

// 1、 通过h函数来创建一个vnode
            const vnode = h('div', {
                class: 'vnode'
            }, [
                h('h2', null, '当前计数:100'),
                h('button',{onClick:function(){}}, "+1")
            ])
            
            // 2、通过mount函数,将vnode挂载到#app上
            mount(vnode,document.getElementById('app'))
            // 3、创建一个新的vnode
            const vnode1 = h('div', {
                class: 'jerry'
            }, 'jerry')
            patch(vnode,vnode1)

此时页面就成为:

vue2和vue3写法上的区别

主要是在获取h函数以及事件绑定上有区别

vue2

const h = this.$createElement;
const vnode = h('div', {
  class: 'v-node-ele',
  on: {
    click: () => {
      console.log('点击事件')
    }
  }
}, '虚拟节点内容')

vue3

import { h } from 'vue';
const vnode = h('div', {
  class: 'v-node-ele',
  onClick: () => {
    console.log('点击事件')
  }
}, h(
  'span', null, 'children内容'
))

参考:

https://www.cnblogs.com/keyeking/p/16112165.html

https://blog.csdn.net/txf666/article/details/124755693

https://blog.csdn.net/qq_42009005/article/details/122986362

目录
打赏
0
0
0
0
12
分享
相关文章
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
198 0
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
165 17
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
88 1
|
3月前
|
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
371 4
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
240 77
Vue 文件批量下载组件封装完整使用方法及优化方案解析
本文详细介绍了批量下载功能的技术实现与组件封装方案。主要包括两种实现方式:**前端打包方案(基于file-saver和jszip)** 和 **后端打包方案**。前者通过前端直接将文件打包为ZIP下载,适合小文件场景;后者由后端生成ZIP文件流返回,适用于大文件或大量文件下载。同时,提供了可复用的Vue组件`BatchDownload`,支持进度条、失败提示等功能。此外,还扩展了下载进度监控和断点续传等高级功能,并针对跨域、性能优化及用户体验改进提出了建议。可根据实际需求选择合适方案并快速集成到项目中。
199 17
Vue 手风琴实现的三种常用方式及长尾关键词解析
手风琴效果是Vue开发中常见的交互组件,可节省页面空间、提升用户体验。本文介绍三种实现方式:1) 原生Vue结合数据绑定与CSS动画;2) 使用Element UI等组件库快速构建;3) 自定义指令操作DOM实现独特效果。每种方式适用于不同场景,可根据项目需求选择。示例包括产品特性页、后台菜单及FAQ展示,灵活满足多样需求。附代码示例与资源链接,助你高效实现手风琴功能。
108 10
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
145 8
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问