// 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上。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。