首页 > 编程语言 >vue3源码学习2-创建和渲染vnode

vue3源码学习2-创建和渲染vnode

时间:2022-08-31 09:45:34浏览次数:50  
标签:container isSVG vnode 源码 vue3 n1 null anchor

创建vnode

我们在第一节中在packages/runtime-core/src/apiCreateApp.ts文件的createAppAPI方法中,app.mount()时:

// 通过 createVNode 方法创建了根组件的vnode
const vnode = createVNode(rootComponent, rootProps)

packages/runtime-core/src/vnode.ts中查看createVNode的实现:

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
	if (props) {
		// 标准化一些class和style
	}
	// 对vnode类型编码

	const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0

	return createBaseVNode(
	    type,
	    props,
	    children,
	    patchFlag,
	    dynamicProps,
	    shapeFlag,
	    isBlockNode,
	    true
  )
}

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
	// 标准化子节点,把不同数据类型的children转成数组或者文本类型 
	normalizeChildren(vnode, children)
	return vnode
}

渲染vnode

还是在packages/runtime-core/src/apiCreateApp.ts文件的createAppAPI方法中,app.mount()时,我们使用了render(vnode, rootContainer, isSVG)来渲染vnode,这方法来自packages/runtime-core/src/renderer.ts文件的baseCreateRenderer方法中调用createAppAPI时传入,所以我们在renderer.ts文件的baseCreateRenderer方法中查看:

const render: RootRenderFunction = (vnode, container, isSVG) => {
	if (vnode == null) {
	// 如果vnode是null, 销毁组件
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
	// 更新或创建组件
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
	// 缓存组件, 表示已经渲染
	container._vnode = vnode
}

接下来看patch的实现,patch是补丁的意思,可以根据vnode挂载DOM,也可以根据新旧vnode更新DOM

// n1表示旧的vnode,n1为null时,表示第一次挂载
// n2表示新的vnode,会根据这个vnode的类型执行不同的逻辑
// container表示容器,vnode渲染生成DOM后,会挂载到container下面
const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
  ) => {
	// 如果没有更新就返回
    if (n1 === n2) {
      return
    }
	// 存在新旧节点,而且类型不同,销毁旧节点
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }
	
	const { type, ref, shapeFlag } = n2
    switch (type) {
      case Text:
		// 处理文本节点
        processText(n1, n2, container, anchor)
        break
      case Comment:
		// 处理注释节点
        processCommentNode(n1, n2, container, anchor)
        break
      case Static:
		// 处理静态节点
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      case Fragment:
		// 处理片段
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
		 // 处理普通DOM元素
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
		  // 处理组件
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
		  // 处理TELEPORT...
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
		  // 处理SUSPENSE...
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }
}

因为初始化的时候传进来的是APP组件,所以先看组件处理processComponent

const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
	// 如果n1为null。则执行挂载组件
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
	  // 否则执行更新组件
      updateComponent(n1, n2, optimized)
    }
  }

继续看mountComponent方法

const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
	// 创建组件实例:通过对象的方式去创建了当前渲染的组件实例
	const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
	// 设置组件实例
	setupComponent(instance)
	// 设置并运行带副作用的渲染函数
	setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
}

创建和设置组件实例后面再看,先看运行带副作用的渲染函数setupRenderEffect方法

const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
	// 创建响应式的副作用渲染函数
    const componentUpdateFn = () => {
		if (!instance.isMounted) {
			// 渲染组件生成子树vnode
			const subTree = (instance.subTree = renderComponentRoot(instance))
			// 把子树挂载到container
			patch(
	            null,
	            subTree,
	            container,
	            anchor,
	            instance,
	            parentSuspense,
	            isSVG
	        )
			// 保留渲染生成的子树的根节点
			initialVNode.el = subTree.el
			instance.isMounted = true
		} else {
			// 更新组件。。。
		}
	}
	// 利用ReactiveEffect创建的副作用渲染函数,副作用的意思就是当instance数据变化时,
	// ReactiveEffect包裹的componentUpdateFn方法会重新执行一遍,即重新渲染组件

	// create reactive effect for rendering
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update),
      instance.scope // track it in component's effect scope
    ))
}

patch中的组件处理先分析到这里,我们接着看处理普通DOM元素 processElement

const processElement = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    isSVG = isSVG || (n2.type as string) === 'svg'
	// 如果n1是null,挂载元素节点
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
	// 否则更新元素节点
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

接着看挂载元素节点方法mountElement

const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
	let el: RendererElement
    const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
	// 创建DOM元素节点
	el = vnode.el = hostCreateElement(
        vnode.type as string,
        isSVG,
        props && props.is,
        props
      )
	  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
	    // 处理子节点是纯文本的情况
        hostSetElementText(el, vnode.children as string)
      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
		// 处理子节点是数组的情况
        mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type !== 'foreignObject',
          slotScopeIds,
          optimized
        )
      }

	 if (props) {
		// 处理props: class、style、event等属性
        for (const key in props) {
          if (key !== 'value' && !isReservedProp(key)) {
            hostPatchProp(
              el,
              key,
              null,
              props[key],
              isSVG,
              vnode.children as VNode[],
              parentComponent,
              parentSuspense,
              unmountChildren
            )
          }
        }
	// 把创建的DOM元素节点挂载到container上
	hostInsert(el, container, anchor)
}

hostCreateElement创建DOM元素,与平台相关,在WEB环境下定义:(packages/runtime-dom/src/nodeOps.ts)

// 内部实现是调用底层DOM API: document.createElement 创建元素
createElement: (tag, isSVG, is, props): Element => {
    const el = isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined)

    if (tag === 'select' && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
    }
    return el
  },

子节点的处理:如果是纯文本,在WEB环境下定义:(packages/runtime-dom/src/nodeOps.ts)

setElementText: (el, text) => {
    el.textContent = text
},

子节点的处理:如果是数组

const mountChildren: MountChildrenFn = (
    children,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    slotScopeIds,
    optimized,
    start = 0
  ) => {
    for (let i = start; i < children.length; i++) {
	  // 预处理child
      const child = (children[i] = optimized
        ? cloneIfMounted(children[i] as VNode)
        : normalizeVNode(children[i]))
	  // 递归patch挂载child
      patch(
        null,
        child,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

最后使用hostInsert把创建的DOM挂载到container,这个方法也是平台相关(packages/runtime-dom/src/nodeOps.ts)

insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
},

标签:container,isSVG,vnode,源码,vue3,n1,null,anchor
From: https://www.cnblogs.com/caicai521/p/16641892.html

相关文章

  • vue3 基础-表单元素双向绑定
    通常是在form表单相关的场景中会用到双向绑定相关,核心是v-model的应用.input输入框<!DOCTYPEhtml><htmllang="en"><head><title>input</title><script......
  • VUE3.0+Antdv+Asp.net WebApi开发学生信息管理系统(完)
    在B/S系统开发中,前后端分离开发设计已成为一种标准,而VUE作为前端三大主流框架之一,越来越受到大家的青睐,Antdv是Antd在Vue中的实现。本系列文章主要通过Antdv和Asp.netWebA......
  • 父传子 子穿父 vue3
    不错的博客https://blog.csdn.net/qq_43895215/article/details/124626692......
  • Vue3打包部署Nginx
     1、在vue.config.js中配置如下1const{defineConfig}=require('@vue/cli-service')2module.exports=defineConfig({3transpileDependencies:tr......
  • SpringMvc请求流程源码解析
    目录SpringMvc请求流程图请求流程粗讲解方法细讲doDispatcher-->核心找到Handler#getHandlergetHandler(request)mapping.getHandler(request)getHandlerInternal()looku......
  • vue3+ pinia 的初实用
    固定不变的:stores/index.jsimport{createPinia}from"pinia"constpinia=createPinia()exportdefaultpinia main.jsimport{createApp}from......
  • LinkedHashMap源码及LRU实现原理
    基本认识LinkedHashMap位于java.util包,于JDK1.4引入,属于JavaCollectionsFramework的成员。查看其UML关系如下图所示:HashMap在很多场景下都满足K-V的存取,而且在非多线......
  • vue3 Teleport 传送门
    先放个官方文档链接~某位同事研究vue3时,发现vue3的Teleport使用起来有点问题。<template><divclass="test">1<divclass="qwe">2</div><teleportto=".q......
  • C# ScottPlot 绘图控件 源码阅读心得体会
    原文:https://www.cnblogs.com/HelloQLQ/p/15643373.htmlScottPlot的介绍可以看这篇博客:https://www.cnblogs.com/myshowtime/p/15606399.html我对代码的理解是这样的:图......
  • 视频直播app源码,实现渐变文字 ,文字换行,加空格
    视频直播app源码,实现渐变文字,文字换行,加空格1、渐变文字,其实是偷换了概念运用背景色的渐变   .text:{font-family:STSongti-SC-Black; font-size:28px; backg......