• Vue源码学习(五):<templete>渲染第四步,生成虚拟dom并将其转换为真实dom


    好家伙,

     

    前情提要:

    在上一篇我们已经成功将ast语法树转换为渲染函数

     现在我们继续

     

    1.项目目录

    代码已开源https://github.com/Fattiger4399/analytic-vue.git手动调试一遍,

    胜过我解释给你听一万遍


    新增文件:vnode/index.js    vnode/patch.js    lifecycle.js

     

    2.虚拟节点

    2.1.什么是虚拟节点?

    虚拟节点(Virtual Node)是前端开发中的一个概念,是一个轻量级的JavaScript对象,用来描述真实DOM树中的一个节点(元素)。

    虚拟节点包括了该节点的标签名、属性、子节点等信息。

    在一些前端框架(比如Vue.js、React等)中,使用虚拟节点来表示整个DOM结构,通过对比新旧虚拟节点,确定发生了哪些变化,并且最小化对真实DOM的操作。

    这种方式可以提高性能,避免频繁地直接操作真实DOM带来的性能消耗。

     

    2.2.为什么要用虚拟节点?

    使用虚拟节点的主要原因是为了提高性能和渲染效率。下面是一些使用虚拟节点的好处:

      1.减少直接操作真实DOM带来的性能损耗:直接对真实DOM进行频繁的增删改查操作会导致浏览器进行重排和重绘,这是非常耗费性能的。

         而使用虚拟节点,可以进行批量的DOM操作,最终只对差异部分进行真实DOM的操作,大大减少了性能损耗

      2.最小化真实DOM的操作次数:虚拟DOM通过对比新旧虚拟节点的差异,找出需要更新的部分,然后只对这部分进行真实DOM的操作,避免不必要的操作。

         这可以减少页面重新渲染的次数,加快页面的响应速度。

      3.提供更灵活的渲染策略:虚拟节点提供了一种对DOM进行抽象的方式,可以根据具体的需求,实现更灵活的渲染策略。

       例如,可以通过操作虚拟节点来实现页面的动态更新、条件渲染、组件化等功能。

      4.跨平台能力:使用虚拟节点,开发者可以将同一套代码渲染到不同的平台(如浏览器、移动端、桌面端等),只需要针对不同平台进行虚拟DOM的适配即可,提高了代码的复用性和跨平台能力。

       总而言之,使用虚拟节点可以优化DOM操作、提高性能、提供灵活的渲染策略,并具有跨平台的能力,这是使用虚拟节点的主要原因。

     

    2.3.虚拟节点长什么样?

    虚拟节点(Virtual Node)是一个简单的JavaScript对象,用来描述真实DOM中的一个节点(元素)。它包括了节点的标签名、属性、子节点等信息。

    复制代码
    {
      tag: 'div',  // 标签名
      props: {     // 属性
        id: 'myDiv',
        className: 'container',
        style: {
          backgroundColor: 'blue',
          color: 'white'
        }
      },
      children: [  // 子节点
        {
          tag: 'h1',
          props: {
            className: 'title'
          },
          children: ['Hello, World!']
        },
        {
          tag: 'p',
          props: {},
          children: ['This is a paragraph']
        }
      ]
    }
    复制代码

    概括:一个描述节点的对象

     

     

    3.从头开始走一遍

    各种方法封装的太杂了

    感觉有点乱,所以我们要从头开始理清

     

    3.1.vue入口文件  src/index.js  

     

    今天的主角登场了

    3.2.vnode/index.js

    复制代码
    export function renderMixin(Vue) {
        Vue.prototype._c = function () {
            //创建标签
            return createElement(...arguments)
        }
        Vue.prototype._v = function (text) { //文本
            return createText(text)
        }
        Vue.prototype._s = function (val) {
            return val == null?"":(typeof val ==='object')?JSON.stringify(val):val
        }
        Vue.prototype._render = function () { //render函数变成 vnode
            let vm = this
            let render = vm.$options.render
            let vnode = render.call(this)
            // console.log(vnode)
            return vnode
        }
    }
    //vnode只可以描述节点
    
    //创建元素
    function createElement(tag,data={},...children){
        return vnode(tag,data,data.key,children)
    }
    //创建文本
    function createText(text){
        return vnode(undefined,undefined,undefined,undefined,text)
    }
    //创建vnode
    function vnode(tag,data,key,children,text){
        return {
            tag,
            data,
            key,
            children,
            text
        }
    }
    复制代码

     

    我们先来分析创建节点部分

     我们确定vnode的节点由哪几个属性组成就好,这里是标签名、属性、键值、子节点和文本内容

     

    接着往下走

     

    3.3.init.js

     

    3.4.mountComponent()方法

    mounetComponent()方法在lifecycle.js中定义

    复制代码
    import { patch } from "./vnode/patch"
    
    export function mounetComponent(vm,el){
        //源码
        vm._updata(vm._render())
        //(1)vm._render() 将 render函数变成vnode
        //(2)vm.updata()将vnode变成真实dom
    }
    
    export function lifecycleMixin(Vue){
        Vue.prototype._updata =function(vnode){
            console.log(vnode)
            let vm = this
            //两个参数 ()
            vm.$el = patch(vm.$el,vnode)
        }
    }
    
    //(1) render()函数 =>vnode =>真实dom 
    复制代码

     

    _render方法在上方有定义,

    复制代码
    Vue.prototype._c = function () {
            //创建标签
            return createElement(...arguments)
        }
        Vue.prototype._v = function (text) { //文本
            return createText(text)
        }
        Vue.prototype._s = function (val) {
            return val == null?"":(typeof val ==='object')?JSON.stringify(val):val
        }
        Vue.prototype._render = function () { //render函数变成 vnode
            let vm = this
            let render = vm.$options.render
            let vnode = render.call(this)
            // console.log(vnode)
            return vnode
        }
    复制代码

    于是,到此处,我们进入到最核心的两个方法了

     

    3.5. _render()

     

     em,这玩意有点复杂,另外开了一篇来说

    Vue源码学习(六):(支线)渲染函数中with(),call()的使用以及一些思考

     一句话概括,将render的作用域指定为Vue实例后运行

    这里我们拿到了我们要的虚拟节点

    vm._render()返回vnode

     

    接着往下走

    3.6._updata

     

    3.7.来看patch方法

    复制代码
    export function patch(oldVnode,vnode){
        console.log(oldVnode,vnode)
        //(1) 创建新DOM
        let el = createEl(vnode)
        console.log(el)
        //(2) 替换 1) 获取父节点  2)插入 3)删除
        let parentEL = oldVnode.parentNode
        parentEL.insertBefore(el,oldVnode.nextsibling)
        parentEL.removeChild(oldVnode)
        return el
    }
    
    function createEl(vnode){
        //vnode 拆解
        let {tag,data,key,children,text} = vnode
        //判断标签是否为字符串 0:创建标签元素,递归处理子节点   1:文本节点
        if(typeof tag === 'string'){
            vnode.el = document.createElement(tag)
            if(children.length >0){
                children.forEach(child => {
                    //递归
                    vnode.el.appendChild(createEl(child))
                });
            }
        }else{
            vnode.el = document.createTextNode(text)
        }
        return vnode.el
    }
    复制代码

     

    emm,node节点的知识点忘了,赶紧复习一下

     删节点操作

    复制代码
    // 获取旧节点的父节点
    let parentEL = oldVnode.parentNode; 
    
    // 将新节点插入到旧节点的下一个兄弟节点之前
    parentEL.insertBefore(el, oldVnode.nextsibling);
    
    // 从父节点中移除旧节点
    parentEL.removeChild(oldVnode);
    复制代码

     

    至此,我们的真实dom渲染完成了

     

    4.最终效果

    来看看网页

    index.html对应内容

    网页

    由原先的

     变成了

     {{}}模板字符串内容成功渲染

     

     

    5.思考

    问:{{msg}}是在哪一步变成hello的?

    上下文,

    复制代码
    id="app" style="display: block;color: #000">Hello{{msg}}

    张三

    复制代码

    提示:网页调试结果

     评论区回答一波

     

  • 相关阅读:
    基于docker和cri-dockerd部署k8sv1.26.3
    MySQL——进阶
    持续集成/持续部署(3)Jenkins(2)
    Java面试题以及答案(六)Jvm
    Java异常处理机制
    C# 使用SIMD向量类型加速浮点数组求和运算(1):使用Vector4、Vector<T>
    Java 的下载安装教程
    皕杰报表中填报控件显示模式控制问题
    华为HCIE云计算之IPsan存储裸设备映射给Linux主机
    携程“919旅行囤货划算节”两年,已成行业超级IP
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/17702429.html