【Vue3】源码解析-Runtime

文章目录

  • 系列文章
  • packages/runtime-dom/src/index.ts
    • 初始化
    • 创建renderer
    • mount
  • \src\runtime-core\component.js
  • h.ts
  • packages/runtime-core/src/renderer.ts
    • 挂载及卸载DOM节点
    • render
  • packages/runtime-dom/src/nodeOps.ts
  • packages/runtime-core/src/apiCreateApp.ts
    • 创建app
    • mount
  • packages/runtime-core/src/vnode.ts
    • 创建虚拟节点
    • createVNode
  • 来源

系列文章

【前端】Typescript入门
【Vue3】源码解析-前置
【Vue3】源码解析-响应式原理
【Vue3】源码解析-虚拟DOM
【Vue3】源码解析-编绎模块
【Vue3】源码解析-Runtime

packages/runtime-dom/src/index.ts

初始化

vue主入口文件中,引入导出了runtime-dom和compiler,而createApp就是来自runtime-dom

// packages/runtime-dom/src/index.ts

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    injectNativeTagCheck(app) // 在dev环境***册一个方法isNativeTag,挂载到app.config下面
  }

  const { mount } = app
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // ...
  }

  return app
}) as CreateAppFunction<Element>

在该函数内部中通过调用ensureRenderer()和createApp(…args)创建了app实例并把实例返回出去,因此我们可以在app实例中安装插件,设置全局指令等等。这其中又是怎么实现的呢?

创建renderer

ensureRenderer()函数的用途是什么呢?

// packages/runtime-dom/src/index.ts

function ensureRenderer() {
  return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))

我们可以看到调用该函数后返回一个renderer,若没有renderer则调用createRenderer来进行创建。

而这边的createRenderer则是来自runtime-core

// packages/runtime-core/src/index.ts

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}

该函数接收一个RendererOptions作为参数,其实际是调用了baseCreateRenderer并将options传入

传入的RendererOptions是什么?为什么在runtime-dom传入,又在runtime-core拆解。

// packages/runtime-dom/src/index.ts

const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)

mount

当创建完app实例后,现在让我们开始进行mount(‘#app’),让我们重新进入createApp

// packages/runtime-dom/src/index.ts

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)

  if (__DEV__) {
    injectNativeTagCheck(app)
  }

  const { mount } = app // 保存app实例上原本的mount
  // 重写mount
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector) // 获取根元素容器
    if (!container) return
    const component = app._component // 获取根组件,即App
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML // 使用根元素来作为模板
    }
    // clear content before mounting
    container.innerHTML = ''
    const proxy = mount(container) // 调用实例中的mount方法
    if (container instanceof Element) {
      container.removeAttribute('v-cloak') // 删除v-cloak属性
      container.setAttribute('data-v-app', '') // 添加data-v-app属性
    }
    return proxy
  }

  return app
}) as CreateAppFunction<Element>

我们可以看到在上面的代码中,在创建完app之后,先对app实例中的mount方法进行了保存,接着又对mount进行了重写。

重写的mount方法中,先是调用了normalizeContainer(containerOrSelector)来获取根元素容器,containerOrSelector即我们传入的#app

// packages/runtime-dom/src/index.ts

function normalizeContainer(
  container: Element | ShadowRoot | string
): Element | null {
  if (isString(container)) {
    const res = document.querySelector(container) // 进行dom操作选中容器
    if (__DEV__ && !res) {
      // ...
    }
    return res
  }
 // ...
  return container as any
}

在获取到根元素的容器之后,进行判断,将容器原本的html作为根组件的模板,然后清除了容器中原本的html

\src\runtime-core\component.js


// 创建组件实例
export function createComponentInstance(vnode) {
  const component = {
    vnode,
    type:vnode.type
  }
  return component
}

// 初始化组件
export function setupComponent(instance) {
  // TODO
  // initProps()
  // initSlots()

  setupStatefulComponent(instance)


}

// 设置组件状态
function setupStatefulComponent(instance) {
  const Component = instance.type

  const { setup } = Component

  if (setup) {
    const setupResult = setup()

    handleSetupResult(instance, setupResult)
  }

}

// 处理setup的结果
function handleSetupResult(instance, setupResult) {
  // function object
  // TODO funciton

  if (typeof setupResult === "object") {
    instance.setupState = setupResult
  }

  finishComponentSetup(instance)
}

// 完成组件设置
function finishComponentSetup(instance) {
  const Component = instance.render

  if(!Component.render) {
    instance.render = Component.render
  }
}

完成源码初始化组件的逻辑,可以对照着这张流程图看一看代码的运行过程:
在这里插入图片描述

h.ts

/*
// type only
h('div')

// type + props
h('div', {})

// type + omit props + children
// Omit props does NOT support named slots
h('div', []) // array
h('div', 'foo') // text
h('div', h('br')) // vnode
h(Component, () => {}) // default slot

// type + props + children
h('div', {}, []) // array
h('div', {}, 'foo') // text
h('div', {}, h('br')) // vnode
h(Component, {}, () => {}) // default slot
h(Component, {}, {}) // named slots

// named slots without props requires explicit `null` to avoid ambiguity
h(Component, null, {})
**/

import { isArray, isObject } from '@vue/shared';
import { createVNode, isVNode } from './vnode';

export function h(type, propsOrChildren?, children?) {
  const l = arguments.length;

  if (l === 2) {
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren]);
      }
      return createVNode(type, propsOrChildren);
    } else {
      return createVNode(type, null, propsOrChildren);
    }
  } else {
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2);
    } else if (l === 3 && isVNode(children)) {
      children = [children];
    }
    return createVNode(type, propsOrChildren, children);
  }
}

h方法对创建虚拟节点操作进行了二次封装,使用法变得多种多样

packages/runtime-core/src/renderer.ts

挂载及卸载DOM节点

import { isString, ShapeFlags } from '@vue/shared';
import { createVNode, isSameVNode, Text } from './vnode';

export function createRenderer(renderOptions) {
  let {
    insert: hostInsert,
    createElement: hostCreateElement,
    createText: hostCreateText,
    remove: hostRemove,
    setElementText: hostSetElementText,
    setText: hostSetText,
    querySelector: hostQuerySelector,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    patchProp: hostPatchProp,
  } = renderOptions;

  const normalize = (child, i) => {
    if (isString(child[i])) {
      let vnode = createVNode(Text, null, child[i]);
      child[i] = vnode;
      return child[i];
    }
    return child[i];
  };

  // 递归挂载子节点
  const mountChildren = (children, container) => {
    for (let i = 0; i < children.length; i++) {
      let child = normalize(children, i);
      patch(null, child, container);
    }
  };

  const mountElement = (vnode, container) => {
    let { type, props, children, shapeFlag } = vnode;
    // 挂载真实dom到vnode上
    let el = (vnode.el = hostCreateElement(type));
    // 属性
    if (props) {
      for (const key in props) {
        hostPatchProp(el, key, null, props[key]);
      }
    }
    // 子节点处理,& 预算判断是否为某一个类型
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      // 文本
      hostSetElementText(el, children);
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      mountChildren(children, el);
    }

    // 插入真实dom到容器中
    hostInsert(el, container);
  };

  const processText = (n1, n2, container) => {
    if (n1 === null) {
      hostInsert((n2.el = hostCreateText(n2.children)), container);
    } else {
      // 文本内容变化,节点复用
      const el = (n2.el = n1.el);
      if (n1.children !== n2.children) {
        // 更新文本
        hostSetText(el, n2.children);
      }
    }
  };

  const patchProps = (oldProps, newProps, el) => {
    for (let key in newProps) {
      hostPatchProp(el, key, oldProps[key], newProps[key]);
    }
    for (let key in oldProps) {
      if (!newProps[key]) {
        hostPatchProp(el, key, oldProps[key], undefined);
      }
    }
  };

  const unmountChildren = (children) => {
    for (let i = 0; i < children.length; i++) {
      unmount(children[i]);
    }
  };

  // 比较两个节点的差异
  const patchKeyChildren = (c1, c2, el) => {

  };

  // 比较两个节点的子节点,el为当前父节点
  const patchChildren = (n1, n2, el) => {
    const c1 = n1.children;
    const c2 = n2.children;
    const prevShapeFlag = n1.shapeFlag;
    const shapeFlag = n2.shapeFlag;

    // 新值为文本
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      // 旧值为数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 文本 数组
        unmountChildren(c1);
      }
      if (c1 !== c2) {
        // 文本 文本
        hostSetElementText(el, c2);
      }
    } else {
      // 旧值为数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 新值为数组
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 数组 数组 diff
          patchKeyChildren(c1, c2, el); // 全量更新,同级比较
        } else {
          // 空   数组
          unmountChildren(c1);
        }
      } else {
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
          // 空   文本
          // 数组 文本
          hostSetElementText(el, '');
        }
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 数组 空
          // 数组 文本
          mountChildren(c2, el);
        }
      }
    }
  };

  // 先复用节点,然后比较属性,再比较子节点
  const patchElement = (n1, n2) => {
    // 复用节点
    let el = (n2.el = n1.el);
    let oldProps = n1.props || {};
    let newProps = n2.props || {};

    patchProps(oldProps, newProps, el);

    patchChildren(n1, n2, el);
  };

  const processElement = (n1, n2, container) => {
    if (n1 === null) {
      mountElement(n2, container);
    } else {
      // 对比元素
      patchElement(n1, n2);
    }
  };

  const patch = (n1, n2, container) => {
    if (n1 === n2) {
      return;
    }

    // 如果新值与老值完全没有可比性,删除老值,创建新值
    if (n1 && !isSameVNode(n1, n2)) {
      unmount(n1);
      n1 = null;
    }

    const { type, shapeFlag } = n2;

    switch (type) {
      case Text: // 文本
        processText(n1, n2, container);
        break;

      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // 元素
          processElement(n1, n2, container);
        }
        break;
    }
  };

  const unmount = (vnode) => {
    hostRemove(vnode.el);
  };

  const render = (vnode, container) => {
    if (vnode === null) {
      // 卸载dom
      if (container._vnode) {
        unmount(container._vnode);
      }
    } else {
      // 初始化及更新
      patch(container._vnode || null, vnode, container);
    }

    // 缓存下次直接更新
    container._vnode = vnode;
  };

  return { render };
}
// packages/runtime-core/src/renderer.ts

function baseCreateRenderer(
    options: RendererOptions,
    createHydrationFns?: typeof createHydrationFunctions
): any {
	const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    forcePatchProp: hostForcePatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    createComment: hostCreateComment,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    setScopeId: hostSetScopeId = NOOP,
    cloneNode: hostCloneNode,
    insertStaticContent: hostInsertStaticContent
  } = options
  
    // 声明了许多操作函数,约2000行

	return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    }
}

在调用完baseCreateRenderer后主要返回了三个函数:render,hydrate,createApp。

此时renderer便创建完成了

render

当我们拥有这个vnode后,就开始进入渲染阶段了。render(vnode, rootContainer),可以看到传入的参数为vnode以及根元素的容器,接下来让我们继续深入。

不知道你是否还记得,这个render函数是在调用createAPI时传入的第一个参数,因此这个函数来源于runtime-core中的baseCreateRenderer

// packages/runtime-core/src/renderer.ts

const render: RootRenderFunction = (vnode, container) => {
    // (判断进行卸载还是渲染
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true) // 卸载
      }
    } else {
      patch(container._vnode || null, vnode, container) // 创建或更新组件,进行dom diff和渲染
    }
    flushPostFlushCbs() // 回调调度器,使用Promise实现,与Vue2的区别是Vue2是宏任务或微任务来处理的
    container._vnode = vnode // 缓存vnode节点,证明已经渲染完成,方便后续diff操作
  }

在render函数中,对vnode的存在进行了判断,如果为空,则对组件进行销毁,否则将调用patch,创建组件。

接下来让我们继续进入patch函数

// packages/runtime-core/src/renderer.ts

const patch: PatchFn = (
    n1, // 旧
    n2, // 新
    container, // 容器
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
  ) => {
 
    // 如果两者类型不同,则直接卸载n1
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    // 根据不同的节点类型来进行不同的process方法
    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: // 片段(dom数组)
        processFragment(
        	// ...
        )
        break
    default:
         if (shapeFlag & ShapeFlags.ELEMENT) { // 原生节点(div)
          processElement(
           // ...
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) { // 组件,根组件即通过processComponent处理
          processComponent(
           // ...
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) { // 传送组件
          ;(type as typeof TeleportImpl).process(
            // ...
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // 挂起组件-异步渲染
          ;(type as typeof SuspenseImpl).process(
            // ...
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }
  
   // 处理节点之后处理ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2)
    }
}

我们可以看到在创建vnode时定义的shapeFlag,在这里发挥了作用。根组件经过逻辑流程之后也进入了processComponent之中。

packages/runtime-dom/src/nodeOps.ts

// packages/runtime-dom/src/nodeOps.ts

export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },

  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },
	// ...
}

其实就是对于dom操作的封装。那为什么要在runtime-dom中传入,runtime-core拆解?

其实是因为在Vue3中runtime-core和runtime-dom的拆分,runtime-core不应该关心实际的操作,这样当新平台要接入时(比如weex)就可以只实现属于自己平台的nodeOps。

总结:创建renderer的函数调用顺序为

  1. ensureRenderer()
  2. createRenderer()
  3. baseCreateRenderer()

packages/runtime-core/src/apiCreateApp.ts

创建app

当创建完renderer后返回了3个函数,我们可以看到其中createApp实际上是引用了createAppAPI(render, hydrate),所以其实const app = ensureRenderer().createApp(…args)创建app实例时,调用的是createAppAPI的返回值(运用柯里化,返回的是一个函数)

// packages/runtime-core/src/apiCreateApp.ts

export function createAppContext(): AppContext {
  return {
    app: null as any, // 刚创建时为空
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      isCustomElement: NO,
      errorHandler: undefined,
      warnHandler: undefined
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null)
  }
}

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
   	// 检验root props
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }
                      
    const context = createAppContext(); // 创建context
    const installedPlugins = new Set(); // 创建插件列表集合,储存已安装的插件

    let isMounted = false;

    const app: App = (context.app = {
      _component: rootComponent as Component,
      _props: rootProps,
      _container: null,
      _context: context,

      version,

      get config() {},
      set config() {}

      use() {},
      mixin() {},
      component() {},
      mount() {}                                                        
      // ...
                      
    })
		
		return app // 返回创建的app实例
  };
}

看完上面的代码后结果就很清楚了,当我们调用createApp时,返回的app上有着许多函数方法和属性,相信你对这些函数方法并不陌生,这些就是vue2.x中在Vue上的那些API:use、mixin、component,在vue3则是被挂载到了app实例上

需要注意的是:我们在应用中调用的createApp(App),其中的APP就是第一个参数,作为根组件

mount

// packages/runtime-core/src/apiCreateApp.ts

mount(rootContainer: HostElement, isHydrate?: boolean): any {
        if (!isMounted) {
          // 1.创建vnode                                                                     
          const vnode = createVNode(
            rootComponent as ConcreteComponent, // App组件
            rootProps
          )

          vnode.appContext = context // 保存context在根节点上

          // HMR root reload
          if (__DEV__) {
            // ...
          }

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            render(vnode, rootContainer) // 2.进入render,函数来源于runtime-core
          }
                                                                                  
          isMounted = true // 修改状态
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer as any).__vue_app__ = app

          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            devtoolsInitApp(app, version)
          }

          return vnode.component!.proxy // 返回vnode.component的代理
        } else if (__DEV__) {
         // ...
        }
      },

runtime-core中mount方法主要做了两件事:创建vnode和调用render进入渲染。这里我们先简略的介绍一下这两个函数的作用。

在创建vnode时调用了createVNode(),并将根组件作为参数传入。

在得到vnode之后又调用了render()开始进行渲染。

最后mount函数的返回值为vnode.component的代理。

packages/runtime-core/src/vnode.ts

创建虚拟节点

import { isArray, isString, ShapeFlags } from '@vue/shared';

export const Text = Symbol('Text');

export function isVNode(value) {
  return !!(value && value.__v_isVnode);
}

/**
 * 创建虚拟节点
 * @param type 虚拟节点类型
 * @param props 属性
 * @param children 子节点
 */
export function createVNode(type, props, children = null) {
  let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;

  // 虚拟dom,可以跨平台,性能好
  const vnode = {
    __v_isVnode: true, // 是否是虚拟节点
    shapeFlag, // 类型标识
    type, // 节点类型
    props, // 属性
    children, // 子节点
    key: props?.key, // key
    /**
     * 对应的真实节点,后续diff算法比对两个vnode时会替换新的属性值,并更新el
     */
    el: null,
  };

  if (children) {
    let type = 0;

    if (isArray(children)) {
      type = ShapeFlags.ARRAY_CHILDREN;
    } else {
      children = String(children);
      type = ShapeFlags.TEXT_CHILDREN;
    }

    // 通过位运算将当前vnode类型及子节点类型存储起来
    vnode.shapeFlag |= type;
  }

  return vnode;
}

虚拟节点不用考虑平台兼容,并且可以将虚拟节点利用js存储并进行比对后再渲染真实dom,不用频繁操作dom元素,性能更好

createVNode

// packages/runtime-core/src/vnode.ts

export const createVNode = (__DEV__
  ? createVNodeWithArgsTransform
  : _createVNode) as typeof _createVNode

// 实际调用
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0, // patch flag默认为0
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  // ...

  // ...

  // class & style normalization
  // 处理props,标准化calss和style
  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) // 标准化class
    }
    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) // 标准化style
    }
  }

  // 定义shapeFlag
  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT // 根组件shapeFlag
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0

  // ...
  
  // 创建vnode对象
  const vnode: VNode = {
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }

  // ...

  normalizeChildren(vnode, children) // 标准化子节点
 // ...

  return vnode // 返回创建完的vnode
}

可以看到createVNode主要做了四件事:

  • 处理props:标准化class和style,如果是响应式元素则会被克隆
  • 定义shapeFlag:shapeFlag用于对元素进行标记,比如文本、注释、组件等等。主要是为了在render的时候可以根据不同的元素类型来进行不同的patch操作。
  • 创建vnode对象
  • 标准化子节点:把不同数据类型的 children 转成数组或者文本类型

shapeFlag的定义如下:

// packages/runtime-core/src/renderer.ts

const patch: PatchFn = (
    n1, // 旧
    n2, // 新
    container, // 容器
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    optimized = false
  ) => {
 
    // 如果两者类型不同,则直接卸载n1
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }

    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }

    const { type, ref, shapeFlag } = n2
    // 根据不同的节点类型来进行不同的process方法
    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: // 片段(dom数组)
        processFragment(
        	// ...
        )
        break
    default:
         if (shapeFlag & ShapeFlags.ELEMENT) { // 原生节点(div)
          processElement(
           // ...
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) { // 组件,根组件即通过processComponent处理
          processComponent(
           // ...
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) { // 传送组件
          ;(type as typeof TeleportImpl).process(
            // ...
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // 挂起组件-异步渲染
          ;(type as typeof SuspenseImpl).process(
            // ...
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }
  
   // 处理节点之后处理ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentSuspense, n2)
    }
}

我们可以看到在创建vnode时定义的shapeFlag,在这里发挥了作用。根组件经过逻辑流程之后也进入了processComponent之中。

来源

Vue3源码学习之路-实现runtime-core
【Vue3源码Runtime-core篇】 第二章初始化Component
代码先锋网

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/461507.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯选拔赛真题67】python奇偶数位相乘 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python奇偶数位相乘 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python奇偶数位相乘 第十五届蓝桥杯青少年组python比赛选拔赛真题 一…

FFT-相干采样和绘制信号被采样后的频谱方法

1.相干采样&#xff1a;要保证后一个输入信号周期内被采样的点和前一个周期的点有一点差别&#xff0c;避免只采到每个周期内一样点从而掩盖了真实性能。所以需要fs/finM/N为无理数&#xff0c;并且为了尽可能多的采到不同值&#xff0c;fs/fin取大些。例如fs/fin5Ghz/570Mhz50…

ChatGPT编程—实现小工具软件(文件查找和筛选)

ChatGPT编程—实现小工具软件(文件查找和筛选) 今天借助[小蜜蜂AI][https://zglg.work]网站的ChatGPT编程实现一个功能&#xff1a;根据特定需求结合通配符和其他条件来进行文件查找和筛选。在这个例子中&#xff0c;我们将创建一个函数find_files&#xff0c;它接受用户输入的…

机器学习-04-分类算法-03KNN算法

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中分类算法&#xff0c;本篇为分类算法与knn算法部分。 本门课程的目标 完成一个特定行业的算法应用全过程&#xff1a; 懂业务会选择合适的算法数据处理算法训练算法调优算法融合 算法评估持续调优工程化…

考察c语言关键字

C语言——关键字 1.问题&#xff1a;简述goto语句的作用 答&#xff1a;无条件跳转 具体来说&#xff0c;其作用在于允许程序在执行时无条件地跳转到指定的标签位置&#xff0c;并从该标签位置继续执行。通过goto语句&#xff0c;可以实现程序流程的无条件转移&#xff0c;使得…

【CKA模拟题】查询消耗CPU最多的Pod

题干 For this question, please set this context (In exam, diff cluster name) 对于此问题&#xff0c;请设置此上下文&#xff08;在考试中&#xff0c;diff 集群名称&#xff09; kubectl config use-context kubernetes-adminkubernetesFind the pod that consumes the …

排成一行的正方形染色问题

有r个正方形排成一行,今用红、黄、白、蓝四种颜色给这个r个正方形染色&#xff0c;每个正方形只能染一种颜色&#xff0c;如果要求染红、黄、白色的正方形分别至少出现一个&#xff0c;问有多少种不同的染色法? 从红、黄、白、蓝4种颜色中,允许重复的取出r个进行排列,其中红…

【汇编】#5 80x86指令系统其一(数据传送与算术)

文章目录 一、数据传送指令1. 通用数据传送指令1.1 MOV传送指令tips:MOV指令几条特殊规定 1.2 XCHG交换指令1.3 进栈指令PUSH1.4 出栈指令POP1.5 所有寄存器进出栈指令PUSHA/POPAtips:SP特别处理 2. 累加器专用传送指令2.1 输入指令IN2.2 OUT输出指令2.3 IO端口与8086CPU通讯关…

栈及其Java实现

栈及其Java实现 ​ 栈&#xff08;Stack)又名堆栈&#xff0c;是允许在同一端进行插入和删除操作的特殊线性表。其中&#xff0c;允许进行插入和删除操作的一端叫做栈顶&#xff08;Top&#xff09;,另一端叫做栈底&#xff08;Bottom&#xff09;,栈底固定&#xff0c;栈顶浮…

常见面试题之计算机网络

1. OSI 五层模型&#xff08;或七层模型&#xff09;是什么&#xff0c;每一层的作用是什么 应用层&#xff1a;又可细分为应用层、表示层、会话层。其中应用层主要做的工作就是为应用程序提供服务&#xff0c;常见的协议为 HTTP、HTTPS、DNS等&#xff1b;表示层主要做的工作…

如何从笔记本电脑恢复已删除的照片

人们相信照片是回忆一生中最难忘事件的最佳媒介。人们在计算机上收集超过 5 GB 的照片是很常见的。然而&#xff0c;在笔记本电脑上保存照片并不安全&#xff0c;因为您可能会因以下原因有意或无意地删除笔记本电脑上的照片&#xff1a; 您的笔记本电脑存储空间几乎已满。您必…

模型、算法、数据模型、模型结构是什么?它们之间有什么关联和区别?

模型、算法、数据模型、模型结构是什么&#xff1f;它们之间有什么关联和区别&#xff1f; 导读一、算法1、算法定义2、机器学习算法定义 二、模型1、模型定义2、数据模型定义3、机器学习模型定义 三、模型结构1、线性模型2、基于实例的模型3、决策树模型4、支持向量机5、集成方…

分析基于解析物理模型的E模式p沟道GaN高电子迁移率晶体管(H-FETs)

来源&#xff1a;Analyzing E-Mode p-Channel GaN H-FETs Using an Analytic Physics-Based Compact Mode&#xff08;TED 24年&#xff09; 摘要 随着近期对用于GaN互补技术集成电路&#xff08;ICs&#xff09;开发的p沟道GaN器件研究兴趣的激增&#xff0c;一套全面的模型…

程序员要失业?全球首位“AI程序员”Deven真的适合职场吗

制造Devin的公司&#xff0c;是一家叫Cognition的10人初创公司&#xff0c;才成立不到2个月。 一、引言 一家成立不到两个月但拥有十名天才工程师的初创公司Cognition&#xff0c;搞了一个引爆科技圈的大动作。 他们推出了一款名为Devin的人工智能&#xff08;AI&#xff09;助…

C语言数据结构易错知识点(3)(堆)

1.堆的本质&#xff1a;完全二叉树 堆在物理结构上是顺序结构&#xff0c;实现方式类似于顺序表&#xff08;数组&#xff09;&#xff1b;但在逻辑结构上是树形结构&#xff0c;准确来说堆是一棵完全二叉树。因为堆的实现在代码上和顺序表有很高的相似度&#xff0c;所以在写…

机试:偶数分解

题目描述: 代码示例: #include <bits/stdc.h> using namespace std; int main(){ // 算法思想1:遍历小于该偶数的所有素数,存入数组中,遍历数组找出两个数之和等于偶数的数int n;cout << "输入样例" << endl;cin >> n;int nums[n];int k …

Android 地图 判断一点是否在某区域内

问题 Android 地图 判断一点是否在某区域内 详细问题 笔者进行Android项目开发&#xff0c;接入高德地图绘制区域后&#xff0c;需要判断一点是否在某区域内&#xff0c;如何实现 代码 mMapView.getMap().addPolygon(polygonOptions).contains(latLng)代码含义解释 这段代…

【C#】【SAP2000】读取SAP2000中frame单元列表到Grasshopper中

private void RunScript(bool build, ref object p1, ref object p2, ref object Profile, ref object stressRatio, ref object temperatureLoad, ref object displacement, ref object frameList){if (build true){// 声明变量int ret;int Numit 0;int[] ObjType new int[…

在Vue中使用wangeditor创建富文本编辑器的完整指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Jmeter+Ant 接口自动化环境配置指南

一 、Jmeter安装与配置 https://blog.csdn.net/tester_sc/article/details/80746405 注&#xff1a;Jmeter5.0的环境变量配置与4.0或历往老版本有部分小差异&#xff0c;笔者用的Jmeter 5.0 二 、Ant的安装与配置 # Ant下载地址(下载到指定目录后&#xff0c;进行解压到当前…