专栏首页纸上得来终觉浅Vue3源码阅读笔记之vnode定义
原创

Vue3源码阅读笔记之vnode定义

// vnode定义:一种模拟dom节点的数据结构,其中包含的信息可以支撑vue去生成实际的dom节点,并正确同步所有用户设置的信息到dom上。
// 来看看vue中vnode相关实现:

// 检测vnode类型
function isVNode(value) {
    return value ? value.__v_isVNode === true : false;
}

// 是否可以算为同一类型的vnode
function isSameVNodeType(n1, n2) {
    // hmr特殊情况先忽略
    if (n2.shapeFlag & 6 /* COMPONENT */ &&
        hmrDirtyComponents.has(n2.type)) {
        // HMR only: if the component has been hot-updated, force a reload.
        return false;
    }
    // 对比 type 和 key 如果是 key 都是 undefined 也是可以的
    return n1.type === n2.type && n1.key === n2.key;
}

 // 创建vnode节点
 function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
    // NULL_DYNAMIC_COMPONENT 情况创建注释节点
    if (!type || type === NULL_DYNAMIC_COMPONENT) {
        if (!type) {
            warn(`Invalid vnode type when creating vnode: ${type}.`);
        }
        type = Comment;
    }
    // 参数如果已经是vnode的情况 克隆一个
    if (isVNode(type)) {
        // createVNode receiving an existing vnode. This happens in cases like
        // <component :is="vnode"/>
        // #2078 make sure to merge refs during the clone instead of overwriting it
        const cloned = cloneVNode(type, props, true /* mergeRef: true */);
        if (children) {
            normalizeChildren(cloned, children);
        }
        return cloned;
    }
    // class component normalization.
    if (isClassComponent(type)) {
        type = type.__vccOpts;
    }
    // class & style normalization.
    // 格式化vnode上的prop信息
    if (props) {
        // for reactive or proxy objects, we need to clone it to enable mutation.
        if (isProxy(props) || InternalObjectKey in props) {
            props = extend({}, props);
        }
        let { class: klass, style } = props;
        if (klass && !isString(klass)) {
            props.class = normalizeClass(klass);
        }
        if (isObject(style)) {
            // reactive state objects need to be cloned since they are likely to be
            // mutated
            if (isProxy(style) && !isArray(style)) {
                style = extend({}, style);
            }
            props.style = normalizeStyle(style);
        }
    }
    // encode the vnode type information into a bitmap
    // 从这里可以看出 type支持的类型很多 意味着 调用这个createVNode方法的时候 对type的限制很少,它可以是一个 配置对象(即组件),也可以是一些字符,或者几种内置的组件对象类型
    // vnode本身对要表示的元素类型约束很少 而在render过程中才分别对不同的type做了不同的处理 在那个地方我们再详细看看
    const shapeFlag = isString(type)
        ? 1 /* ELEMENT */
        : isSuspense(type)
            ? 128 /* SUSPENSE */
            : isTeleport(type)
                ? 64 /* TELEPORT */
                : isObject(type)
                    ? 4 /* STATEFUL_COMPONENT */
                    : isFunction(type)
                        ? 2 /* FUNCTIONAL_COMPONENT */
                        : 0;
    if (shapeFlag & 4 /* STATEFUL_COMPONENT */ && isProxy(type)) {
        type = toRaw(type);
        warn(`Vue received a Component which was made a reactive object. This can ` +
            `lead to unnecessary performance overhead, and should be avoided by ` +
            `marking the component with \`markRaw\` or using \`shallowRef\` ` +
            `instead of \`ref\`.`, `\nComponent that was made reactive: `, type);
    }
    // 下面的信息就是vue过程会用到的字段
    // 信息很多 有些字段在之后的分析再讲
    const vnode = {
        // 标记自己
        __v_isVNode: true,
        ["__v_skip" /* SKIP */]: true,
        // 原始type
        type,
        // 用户设置的props
        props,
        // 用户设置的key
        key: props && normalizeKey(props),
        // 用户设置的ref
        ref: props && normalizeRef(props),
        scopeId: currentScopeId,
        // 子节点 树形结构
        children: null,
        // 组件实例对象
        component: null,
        // 内置组件相关字段
        suspense: null,
        ssContent: null,
        ssFallback: null,
        // 指令
        dirs: null,
        // 内置组件相关字段
        transition: null,
        // vnode实际被转换为dom元素的时候产生的元素
        el: null,
        anchor: null,
        target: null,
        targetAnchor: null,
        // 静态节点数
        staticCount: 0,
        shapeFlag,
        patchFlag,
        dynamicProps,
        dynamicChildren: null,
        // app上下文
        appContext: null
    };
    // validate key
    if (vnode.key !== vnode.key) {
        warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type);
    }
    // 格式化子节点
    normalizeChildren(vnode, children);
    // normalize suspense children
    // SUSPENSE 相关
    if (shapeFlag & 128 /* SUSPENSE */) {
        const { content, fallback } = normalizeSuspenseChildren(vnode);
        vnode.ssContent = content;
        vnode.ssFallback = fallback;
    }
    // vue3的优化方案 先不看
    if (shouldTrack$1 > 0 &&
        // avoid a block node from tracking itself
        !isBlockNode &&
        // has current parent block
        currentBlock &&
        // presence of a patch flag indicates this node needs patching on updates.
        // component nodes also should always be patched, because even if the
        // component doesn't need to update, it needs to persist the instance on to
        // the next vnode so that it can be properly unmounted later.
        (patchFlag > 0 || shapeFlag & 6 /* COMPONENT */) &&
        // the EVENTS flag is only for hydration and if it is the only flag, the
        // vnode should not be considered dynamic due to handler caching.
        patchFlag !== 32 /* HYDRATE_EVENTS */) {
        currentBlock.push(vnode);
    }
    return vnode;
}

// 主要是看下vnode有哪些信息 以及各个字段的语义

// 克隆vnode 基本上是复制合并原有属性 返回一个新vnode对象 同时维持与原有父子节点的引用关系
function cloneVNode(vnode, extraProps, mergeRef = false) {
    // This is intentionally NOT using spread or extend to avoid the runtime
    // key enumeration cost.
    const { props, ref, patchFlag, children } = vnode;
    // 合并新的props
    const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
    return {
        __v_isVNode: true,
        ["__v_skip" /* SKIP */]: true,
        type: vnode.type,
        props: mergedProps,
        key: mergedProps && normalizeKey(mergedProps),
        ref: extraProps && extraProps.ref
            ? // #2078 in the case of <component :is="vnode" ref="extra"/>
                // if the vnode itself already has a ref, cloneVNode will need to merge
                // the refs so the single vnode can be set on multiple refs
                mergeRef && ref
                    ? isArray(ref)
                        ? ref.concat(normalizeRef(extraProps))
                        : [ref, normalizeRef(extraProps)]
                    : normalizeRef(extraProps)
            : ref,
        scopeId: vnode.scopeId,
        children: patchFlag === -1 /* HOISTED */ && isArray(children)
            ? children.map(deepCloneVNode)
            : children,
        target: vnode.target,
        targetAnchor: vnode.targetAnchor,
        staticCount: vnode.staticCount,
        shapeFlag: vnode.shapeFlag,
        // if the vnode is cloned with extra props, we can no longer assume its
        // existing patch flag to be reliable and need to add the FULL_PROPS flag.
        // note: perserve flag for fragments since they use the flag for children
        // fast paths only.
        // 修改 patchFlag 如注释所言
        patchFlag: extraProps && vnode.type !== Fragment
            ? patchFlag === -1 // hoisted node
                ? 16 /* FULL_PROPS */
                : patchFlag | 16 /* FULL_PROPS */
            : patchFlag,
        dynamicProps: vnode.dynamicProps,
        dynamicChildren: vnode.dynamicChildren,
        appContext: vnode.appContext,
        dirs: vnode.dirs,
        transition: vnode.transition,
        // These should technically only be non-null on mounted VNodes. However,
        // they *should* be copied for kept-alive vnodes. So we just always copy
        // them since them being non-null during a mount doesn't affect the logic as
        // they will simply be overwritten.
        component: vnode.component,
        suspense: vnode.suspense,
        // suspense 有关的2个slot
        ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
        ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
        el: vnode.el,
        anchor: vnode.anchor
    };
}
/**
 * Dev only, for HMR of hoisted vnodes reused in v-for
 * https://github.com/vitejs/vite/issues/2022
 */
function deepCloneVNode(vnode) {
    const cloned = cloneVNode(vnode);
    if (isArray(vnode.children)) {
        cloned.children = vnode.children.map(deepCloneVNode);
    }
    return cloned;
}

// text node类型 vnode
function createTextVNode(text = ' ', flag = 0) {
    return createVNode(Text, null, text, flag);
}
/**
 * @private
 */
// 静态vnode 代表这个节点内容是不会改变的 没有动态属性
function createStaticVNode(content, numberOfNodes) {
    // A static vnode can contain multiple stringified elements, and the number
    // of elements is necessary for hydration.
    const vnode = createVNode(Static, null, content);
    // 静态节点统计
    vnode.staticCount = numberOfNodes;
    return vnode;
}
/**
 * @private
 */
// 注释节点
function createCommentVNode(text = '', 
// when used as the v-else branch, the comment node must be created as a
// block to ensure correct updates.
asBlock = false) {
    return asBlock
        ? (openBlock(), createBlock(Comment, null, text))
        : createVNode(Comment, null, text);
}

// 格式化输出vnode 确保一定返回一个vnode节点 哪怕是一个注释占位符
function normalizeVNode(child) {
    if (child == null || typeof child === 'boolean') {
        // empty placeholder
        return createVNode(Comment);
    }
    else if (isArray(child)) {
        // fragment
        return createVNode(Fragment, null, child);
    }
    else if (typeof child === 'object') {
        // already vnode, this should be the most common since compiled templates
        // always produce all-vnode children arrays
        // 大部分情况都是直接处理vnode的
        // 初次插入 直接使用vnode
        // 更新的情况就先复制一次
        return child.el === null ? child : cloneVNode(child);
    }
    else {
        // strings and numbers
        return createVNode(Text, null, String(child));
    }
}

// 总结一下:vnode本身只是一种树形结构信息用来描述真实dom树结构的,vue用它来存储用户标记的一些信息,然后在初始化和更新2种操作对vnode进行对比,然后把变化同步到真实dom上。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vue3 源码解析(五):Patch 算法

    与 React 一样,Vue 在处理虚拟 DOM 的更新时,也有自己的 diff 算法 —— patch。

    Originalee
  • Vue3源码阅读笔记之$emit实现

    总结:组件实例上的 emit 方法其实就是调用props中从父组件传进来的一个箭头函数。

    wanyicheng
  • Vue3源码阅读笔记之事件队列

    总结一下:vue中的事件队列分3种,vue内部实现中主要是把render函数的包裹体effect放到queue队列中。

    wanyicheng
  • Vue3源码阅读笔记之异步组件

    wanyicheng
  • Vue3.0 七大亮点是什么??

    在vue3中,增加了静态标记PatchFlag。在创建vnode的时候,会根据vnode的内容是否可以变化,为其添加静态标记PatchFlag。diff的时候,...

    逆锋起笔
  • Vue3源码阅读笔记之数据响应式

    总结:Vue3中的数据响应式实现是一个较为独立的实现,适合单独分析学习哈。上文是删除了部分支线逻辑的版本,只保留了主线逻辑,大家如果想看完整的实现,还是建议去读...

    wanyicheng
  • Vue3源码阅读笔记之组件是如何实现

    wanyicheng
  • Vue3中不止composition-api,其他的提案(RFC)也很精彩。

    最近一段时间,Vue3带来的新能力composition-api带来了比较大的轰动,虽然是灵感是源React Hook,但是在很多方面却超越了它。但是除了com...

    ssh_晨曦时梦见兮
  • 写给初中级前端的高级进阶指南(JS、TS、Vue、React、性能、学习规划)

    我曾经一度很迷茫,在学了Vue、React的实战开发和应用以后,好像遇到了一些瓶颈,不知道该怎样继续深入下去。相信这也是很多一两年经验的前端工程师所遇到共同问题...

    ssh_晨曦时梦见兮

扫码关注云+社区

领取腾讯云代金券