• 8.Vue3虚拟节点的实现


    runtime-core包

    vue3中节点的渲染操作主要在runtime-core包中,runtime-core不关心运行的平台。

    createVNode函数

    vue3中通过函数createVNode函数创建一个虚拟节点,其传入三个参数,分别是节点名,属性对象,孩子,如:

    		 const {createVNode,render,h} =  VueRuntimeDOM
             console.log(createVNode('h1',{key:1},'Hello world'))
    
    • 1
    • 2

    调用改函数,创建一个h1标签,输出内容如下:
    在这里插入图片描述

    可以看到,输出的对象就是一个虚拟节点,其对象包含内容如下:

    • children:节点包含的孩子内容,可以是一个字符串,也可能是一个虚拟节点数组
    • el:改虚拟节点后面渲染成真实节点后,会被el记录,即 vnode.el 就是其真实节点
    • key:虚拟节点的标识,也用作区分不同的节点
    • props:虚拟节点的属性
    • shapeFlags:节点的类型标识
    • type:节点名
    • __v_isVNode:判断是否是真实节点

    h函数

    • h函数似乎拥有与createVnode函数类似的功能,如上述例子改成h函数
    console.log(h('h1',{key:1},'Hello world'))
    
    • 1

    在这里插入图片描述

    • 可以看到其结果依然是一个虚拟节点,其实h函数就是在createVNode函数的基础上,做了一层封装,createVNode函数必须传入三个参数,而h函数则不用,如h('h1','Hello world'),内部会将其转换成createVNode('h1,null,'Hello world')

    createVNode函数的实现

    形状标识的创建

    • 虚拟节点中有了type来识别节点类型,为什么还要有shapeFlags类型呢,原因就是为了判断该节点是否有孩子及孩子类型是什么
    • 首先规定不同的孩子类型对应不同的标识号
    export const enum ShapeFlags { // vue3提供的形状标识
        ELEMENT = 1, // 1
        FUNCTIONAL_COMPONENT = 1 << 1, // 2
        STATEFUL_COMPONENT = 1 << 2, // 4
        TEXT_CHILDREN = 1 << 3, // 8
        ARRAY_CHILDREN = 1 << 4, // 16
        SLOTS_CHILDREN = 1 << 5, // 32
        TELEPORT = 1 << 6, // 64 
        SUSPENSE = 1 << 7, // 128
        COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
        COMPONENT_KEPT_ALIVE = 1 << 9,
        COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 而ShapeFlags就是记录这些标识,比如:
      • 一个创建一个vnode,其type为h1
      • 那么其类型属于ELEMENT,
      • 即ShapeFlags = 1
      • 若为其添加一个文本“hello world”,那么改文本节点属于TEXT_CHILDREN
      • 则 ShapeFlags变为 1 | 8 = 9,即shapeFlags变为9
      • 同样拿到一个节点如何判断其包含某个节点呢,如一个节点的 shapeFlags为17,
      • 那么其包含TEXT_CHILDREN吗? 只需 shapeFlags & ShapeFlags.TEXT_CHILDREN
      • 即上述结果变成17 & 8 ,结果为0,则不包含
      • 那么其包含ARRAY_CHILDREN吗,同样操作shapeFlags & ShapeFlags.ARRAY_CHILDREN
      • 即上述结果变为17 & 16 ,结果为1,说明包含
    • 可以看到,通过给不同节点类型不同的编码,然后利用按位与(&)与按位或(|)就可以识别改节点的子节点类型
    • 调用createVNode函数后,会默认创建一个vnode对象,该对象会根据传入的参数设定默认值
      • 首先判断type类型是否为字符串,若是则shapeFlags默认为1
      • 其次判断孩子类型,若是字符串,则 shapeFlags改为 shapeFlags | ShapeFlags.TEXT_CHILDREN
      • 若为数组,则改为 shapeFlags | ShapeFlags.ARRAY_CHILDREN
    • 最终返回节点
    export function createVNode(type,props = null, children = null){
    
        // 后续判断有不同类型的虚拟节点
        let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0 // 标记出来了自己是什么类型
        // 我要将当前的虚拟节点 和 自己儿子的虚拟节点映射起来  权限组合 位运算
        const vnode = { // vnode 要对应真实际的节点
            __v_isVNode:true,
            type,
            props,
            children,
            key: props && props.key,
            el:null,
            shapeFlag
            // 打个标记
        }
        if(children){
            let temp = 0;
            if(isArray(children)){ // 走到createVnode 要么是数组要么是字符串 h()中会对children做处理
                temp = ShapeFlags.ARRAY_CHILDREN
            }else{
                children = String(children);
                temp = ShapeFlags.TEXT_CHILDREN
            }
            vnode.shapeFlag  =  vnode.shapeFlag | temp;
        }
        // shapeFlags  我想知到这个虚拟节点的儿子是数组 还是元素 还是文本 
        return vnode;
    }
    
    
    • 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

    h函数的实现

    • h函数是对createVNode函数的封装,默认接收三个参数,也可以接收两个参数 ,接收两个参数的情况有两种
      • 元素+属性
      • 元素+孩子
    • 首先判断函数传入的参数长度,如果为三个参数
      • 判断第三个参数的类型,如果是单个孩子节点,则封装成数组(孩子只划分为两种:字符串或者孩子节点数组)
      • 若是两个参数
        • 判断第二个参数是否为数组
        • 若是,则说明是孩子数组,完善createVNode的参数即可
        • 若不是,则需要判断这个参数是孩子节点还是属性对象
    export function h(type,propsOrChildren,children){
        // h方法 如果参数为两个的情况  1) 元素 + 属性  2) 元素 + 儿子
        const l = arguments.length;
        if(l === 2){
            // 如果propsOrChildren是对象的话 可能是属性 可能是儿子节点 
            if(isObject(propsOrChildren) && !isArray(propsOrChildren)){ // h(type,属性或者元素对象)
                // 要么是元素对象 要么是属性
                if(isVnode(propsOrChildren)){ // h(type,元素对象) 
                    return createVNode(type,null,[propsOrChildren])
                }
                return createVNode(type,propsOrChildren) // h(type,属性)
            }else{
                // 属性 + 儿子的情况   儿子是数组 或者 字符
                return createVNode(type,null,propsOrChildren) // h(type,[] )  h(type,'文本‘)
            }
        }else{
            if(l === 3 && isVnode(children)){ // h(type,属性,儿子)
                children = [children]
            }else if(l > 3){
                children = Array.from(arguments).slice(2)// h(type,属性,儿子数组)
            }
            return createVNode(type,propsOrChildren,children)
            // l > 3
        }
    }
    
    • 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
  • 相关阅读:
    Python使用yield协程实现生产者消费者问题
    测试的重要性及目的
    suanfa5==前缀数组
    docker部署redis6
    vue3+ts部分场景示例
    RabbitMQ------延迟队列(整合SpringBoot以及使用延迟插件实现真正延时)(七)
    Gd-DOTA,CAS:72573-82-1,钆特酸
    感觉的定义
    (五)Selenium自动化测试实战—PO模式
    学习笔记-FRIDA脚本系列(四)
  • 原文地址:https://blog.csdn.net/wenyeqv/article/details/126077655