您现在的位置是:网站首页> 编程资料编程资料

Vue3源码分析组件挂载创建虚拟节点_vue.js_

2023-05-24 436人已围观

简介 Vue3源码分析组件挂载创建虚拟节点_vue.js_

前情提要

本文我们接着Vue3源码系列(1)-createApp发生了什么?继续分析,我们知道调用createApp方法之后会返回一个app对象,紧接着我们会调用mount方法将节点挂载到页面上。所以本文我们从mount方法开始分析组件的挂载流程。

本文主要内容

  • Vue如何创建组件虚拟节点、文本虚拟节点、注释虚拟节点、静态虚拟节点。
  • ShapeFlags是什么?
  • PatchFlags是什么?
  • 我们在Vue中写的classstyle形式掺杂不一、何时进行的标准化、如何进行标准化。
  • 创建虚拟节点时对插槽的处理。
  • ref可以写三种形式,字符串形式、响应式形式、函数形式、patch阶段如何实现他们的更新和设置。

1. Mount函数

mount(rootContainer) { //判断当前返回的app是否已经调用过mount方法 if (!isMounted) { //如果当前组件已经有了app //实例则已经挂载了警告用户 if (rootContainer.__vue_app__) { console.warn( `There is already an app instance mounted on the host container.\n` + ` If you want to mount another app on the same host container,` + ` you need to unmount the previous app by calling \`app.unmount()\` first.` ); } //创建组件的VNode const vNode = createVNode(rootComponent); //刚才调用createApp创建的上下文 vNode.appContext = context; render(vNode, rootContainer); //渲染虚拟DOM //标记已经挂载了 isMounted = true; //建立app与DOM的关联 app._container = rootContainer; rootContainer.__vue_app__ = app; //建立app与组件的关联 app._instance = vNode.component; } //已经调用过mount方法 警告用户 else { console.warn( `App has already been mounted.\n` + `If you want to remount the same app, move your app creation logic ` + `into a factory function and create fresh app instances for each ` + `mount - e.g. \`const createMyApp = () => createApp(App)\`` ); } } 
  • 这个函数主要判断当前app是否已经调用过mount函数了,如果已经调用过mount函数了那么警告用户。
  • createVnode根据编译后的.vue文件生成对应的虚拟节。
  • render函数用于将createVnode生成的虚拟节点挂载到用户传入的container中。
  • 在介绍创建虚拟节点的方法之前我们先来说说shapeFlag: 它用来表示当前虚拟节点的类型。我们可以通过对shapeFlag做二进制运算来描述当前节点的本身是什么类型、子节点是什么类型。
export const ShapeFlags = { ELEMENT: 1, //HTML SVG 或普通DOM元素 FUNCTIONAL_COMPONENT: 2, //函数式组件 STATEFUL_COMPONENT: 4, //有状态组件 COMPONENT: 6, //2,4的综合表示所有组件 TEXT_CHILDREN: 8, //子节点为纯文本 ARRAY_CHILDREN: 16, //子节点是数组 SLOTS_CHILDREN: 32, //子节点包含插槽 TELEPORT: 64, //Teleport SUSPENSE: 128, //suspense }; 

2. 创建虚拟节点的几个方法

(1) createVNode:用于创建组件的虚拟节点

function createVNode( type,//编译后的.vue文件形成的对象 // //给组件传递的props props = null, children = null,//子组件 patchFlag = 0,//patch的类型 dynamicProps = null,//动态的props isBlockNode = false//是否是block节点 ) { //通过__vccOpts判断是否是class组件 if (isClassComponent(type)) { type = type.__vccOpts; } //将非字符串的class转化为字符串 //将代理过的style浅克隆在转为标准化 if (props) { //对于代理过的对象,我们需要克隆来使用他们 //因为直接修改会导致触发响应式 props = guardReactiveProps(props); let { class: klass, style } = props; if (klass && !shared.isString(klass)) { props.class = shared.normalizeClass(klass); } if (shared.isObject(style)) { if (reactivity.isProxy(style) && !shared.isArray(style)) { style = shared.extend({}, style); } props.style = shared.normalizeStyle(style); } } //生成当前type的类型 let shapeFlag = 0; /* 这部分我修改了源码,便于读者理解 suspense teleport放到前面是因为 他们本身就是一个对象,如果放到后面 会导致先中标isObject那么shapeFlag 的赋值会出错。 判断当前type的类型,赋值给shapeFlag 后续就可以通过shapeFlag来判断当前虚拟 节点的类型。 */ if (isString(type)) { //div span p等是ELEMENT shapeFlag = ShapeFlags.ELEMENT; } else if (isSuspense(type)) { shapeFlag = ShapeFlags.SUSPENSE; } else if (isTeleport(type)) { shapeFlag = ShapeFlags.TELEPORT; } else if (isObject(type)) { //对象则是有状态组件 shapeFlag = ShapeFlags.STATEFUL_COMPONENT; } else if (isFunction(type)) { //如果是函数代表是无状态组件 shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT; } //调用更基层的方法处理 return createBaseVNode( type, props, children, patchFlag, dynamicProps, shapeFlag, isBlockNode, true ); } 
  • createVNode主要是对传递的type做出判断,通过赋值shapeFlag来标明当前的虚拟节点的类型。
  • 如果props含有style或者class要进行标准化。
  • 例如
    其中第一个是cssText形式、第二个是对象形式,他们应该被转化为对象类型所以转化后应该为
    。当然对于class也需要标准化:class={hello:true,world:false} => :class="hello"。但是这里处理的其实是用户自己写了render函数,而对于使用了Vue自带的编译系统之后,是不需要做这一层处理的。我们可以来看这样一段编译后的代码。
 //编译后 const _hoisted_1 = { class:_normalizeClass({hello:true}), style:_normalizeStyle([{color:'red'},'background:red']) } function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", _hoisted_1)) } 
  • 所以编译后的template,在调用createVNode的时候传递的props就已经是经过处理的了。
  • 我们忽略guardReactiveProps方法,来探寻一下normalizeStyle以及normalizeClass方法
  • normalizeClass: 这个方法用于标准化class。用户可能会写数组形式,对象形式,以及字符串形式,字符串形式和对象形式很好理解,对于数组形式,递归调用了normalizeClass意味着你可以传递多层的数组形式的参数,例如:[{hello:true},[{yes:true}],'good'] => hello yes good
//{hello:true,yes:false}=>"hello" function normalizeClass(value) { let res = ""; if (isString(value)) { res = value; } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { const normalized = normalizeClass(value[i]); if (normalized) { res += normalized + " "; } } } else if (isObject(value)) { for (const name in value) { if (value[name]) { res += name + " "; } } } return res.trim(); } 
  • normalizeStyle: 这个方法用于标准化style。同样用户可以传递数组、对象、字符串三种形式。字符串形式的就是cssText形式,这种形式需要调用parseStringStyle方法。例如:"background:red;color:red"对于这样的字符串需要转化为对象就需要切割";"和":"符号得到key和value然后再转化为对象。同时这也是parseStringStyle的作用,这个函数就不展开讲了。而对象形式则是标准化形式,遍历即可。
//[{backgroundColor:'red',"color:red;"}]=> //{backgroundColor:'red',color:'red'} export function normalizeStyle(value) { if (isArray(value)) { const res = {}; for (let i = 0; i < value.length; i++) { const item = value[i]; const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); if (normalized) { for (const key in normalized) { res[key] = normalized[key]; } } } return res; } else if (isString(value)) { return value; } else if (isObject(value)) { return value; } } 
  • 当然还有判断函数isTeleportisSuspense,对于TeleportSuspense他们是Vue内部实现的组件,所以他们自带属性__isTeleport__Suspense属性。
const isSuspense = type => type.__isSuspense; const isTeleport = type => type.__isTeleport; 

(2) createElementVNode:用于创建普通tag的虚拟节点如

特别提示:

createElementVNode就是createBaseVNode方法,创建组件的虚拟节点方法createVNode必须标准化children,needFullChildrenNormalization=true

function createBaseVNode( type,//创建的虚拟节点的类型 props = null,//传递的props children = null,//子节点 patchFlag = 0,//patch类型 dynamicProps = null,//动态props shapeFlag = type === Fragment ? 0 : 1,//当前虚拟节点的类型 isBlockNode = false,//是否是block needFullChildrenNormalization = false//是否需要标准化children ) { const vnode = { __v_isVNode: true, //这是一个vnode __v_skip: true, //不进行响应式代理 type, //.vue文件编译后的对象 props, //组件收到的props key: props && normalizeKey(props), //组件key ref: props && normalizeRef(props), //收集到的ref scopeId: getCurrentScopeId(),//当前作用域ID slotScopeIds: null, //插槽ID children, //child组件 component: null, //组件实例 suspense: null,//存放suspense ssContent: null,//存放suspense的default的虚拟节点 ssFallback: null,//存放suspense的fallback的虚拟节点 dirs: null, //解析到的自定义指令 transition: null, el: null, //对应的真实DOM anchor: null, //插入的锚点 target: null,//teleport的参数to指定的DOM targetAnchor: null,//teleport插入的锚点 staticCount: 0, shapeFlag, //表示当前vNode的类型 patchFlag, //path的模式 dynamicProps, //含有动态的props dynamicChildren: null, //含有的动态children appContext: null, //app上下文 }; //是否需要对children进行标准化 if (needFullChildrenNormalization) { normalizeChildren(vnode, children); //处理SUSPENSE逻辑 if (shapeFlag & ShapeFlags.SUSPENSE) { //赋值ssContent=>default和ssFallback=>fallback type.normalize(vnode); } } //设置shapeFlags else if (children) { vnode.shapeFlag |= shared.isString(children) ? ShapeFlags.TEXT_CHILDREN : ShapeFlags.ARRAY_CHILDREN; } //警告key不能为NaN if (vnode.key !== vnode.key) { warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type); } //判断是否加入dynamicChildren if ( getBlockTreeEnabled() > 0 && //允许追踪 !isBlockNode && //当前不是block getCurrentBlock() && //currentBlock存在 //不是静态节点,或者是组件 (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) && vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS ) { //放入dynamicChildren getCurrentBlock().push(vnode); } return vnode; } 
  • createBaseVNode: 这个方法用于创建一个虚拟节点,同时对key、ref、chidren(needFullChildren

-六神源码网