Исходный код Vue3 (2): новые функции Vue3 из исходного кода.

Vue.js
Исходный код Vue3 (2): новые функции Vue3 из исходного кода.

автор:Сюй จุ๊บ, несанкционированное воспроизведение запрещено.

предисловие

предыдущий пост«Исходный код Vue3 (1)»кратко представленVue3Структура исходного кода, также изучайте исходный кодVue3Основание также является реактивным. На этот раз давайте узнаем о другом ключевом компоненте и изучим его.Vue3Инициализация компонента и процесс его рендеринга. Если есть какие-то неточности или упущения, просьба исправить и дополнить.


текст

Помните, что я упоминал в предыдущем постеVue3Приложение изначальное?

createApp(App).mount('#app')

В прошлый раз мы узналиcreateApp(App)Благодаря процессу закрытия и каррирования мы можем иметь дело с различными сценариями и платформами, создавать и возвращать конкретные экземпляры приложения, поэтому на этот раз наше исследование начинается сmount('#app')Для начала разберитесь с процессом начального рендеринга.

метод крепления

Оглядываясь назад на содержание предыдущей статьи, мы обнаружили, что в исходном коде есть два основных определения.mountметоды соответственно:

  1. runtime-dom/src/index.ts переписан для веб-платформы браузераmountметод
  const { mount } = app
  app.mount = (containerOrSelector: Element | string): any => {
    // normallizeContainer 这个方法顾名思义统一容器,mount参数可能是DOM对象或者选择器
    // 如果是选择器就取到对应DOM
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    
    // 这里app._component就是我们通过 rootComponent 参数,传入打包编译过的 App 组件(图1)
    const component = app._component
    
    // 如果我们传入的组件没有定义render,没有模版,那就取DOM里面原本内容当作模版
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }
    
    // 这里会清除DOM里原有的内容
    container.innerHTML = ''
    
    // 执行之前暂存的基础的 mount 方法
    const proxy = mount(container)
    container.removeAttribute('v-cloak')
    container.setAttribute('data-v-app', '')
    return proxy
  }

фигура 1:

图

По коду и комментариям внутри метод перезаписи можно разделить на несколько шагов: 1. Получить DOM-контейнер 2. Оценить входящее приложение корневого компонента 3. Выполнить стандартныйmountметод.

  1. runtime-core/src/apiCreateApp.ts, это стандартный кроссплатформенный компонент в экземпляре приложения.mountметод
 mount(rootContainer: HostElement, isHydrate?: boolean): any {
   // app应用是否已经被挂载
   if (!isMounted) {
     // 1. 创建VNode 这里 rootComponent 就是 createApp(App) 传入的 App 组件
     const vnode = createVNode(
       rootComponent as ConcreteComponent,
       rootProps
     )

     // app应用实例存储上下文,主要有 app应用实例本身,各类设置项,配置项
     vnode.appContext = context

     if (isHydrate && hydrate) {
       // 服务端渲染相关
       hydrate(vnode as VNode<Node, Element>, rootContainer as any)
     } else {
       // 2. render 渲染 VNode
       // 这里的render再上一篇文章有提到 ensureRenderer 创建出来的
       render(vnode, rootContainer)
     }
     isMounted = true
     
     // 存储DOM容器
     app._container = rootContainer
     // for devtools and telemetry
     ;(rootContainer as any).__vue_app__ = app
     // ...
     return vnode.component!.proxy
   } else if (__DEV__) {
     // ...
   }
 },

стандарт можно посмотретьmountМетод в основном состоит из следующих шагов: 1. Создание VNode 2. Рендеринг VNode как настоящий DOM

резюме

На данный момент мы знаемmountЧто примерно делает метод.

  1. normalizeContainer получает контейнер DOM
  2. createVNode, согласно входящему компоненту приложения, создать VNode
  3. визуализировать VNode и смонтировать его в DOM-контейнере
  4. Возвращает прокси для VNode.component

Давайте посмотрим, что связано с VNode.

Создание и рендеринг VNode

Я полагаю, что все знакомы с VNode.Проще говоря, он абстрагирует DOM и другие вещи через объекты JavaScript. Если вы спросите о преимуществах в интервью, вы обязательно упомянете следующие моменты: 1. Вам не нужно часто менять DOM, 2. Кроссплатформенность, обеспечиваемая абстракцией, и 3. Преимущество в производительности при работе с VNode. JS по сравнению с прямым управлением DOM. Но недавно прочитав некоторые статьи, я подумал, что третье преимущество не является абсолютным.Для компонентов с большим объемом данных, таких как Дерево и Таблица, процесс зацикливания по под-VNode рендеринга занимает много времени.В итоге, по-прежнему необходимо манипулировать DOM, страница может даже застрять.

Возвращаясь к теме, посмотрите на следующий пример

App.vue
<template>
  <HelloWorld msg="Hello Vue 3.0 + Vite" />
  <p>{{ showText }}</p>
</template>

HelloWorld.vue
<template>
  <div>{{ msg }}</div>
</template>

Создать виртуальный узел

существуетVue3, есть много VNodes, представляющих разные категории, например, в приведенном выше примере.HelloWorldКомпоненты VNode, общие элементы VNodep.

В частности, давайте рассмотрим метод создания VNode.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 (!type || type === NULL_DYNAMIC_COMPONENT) {
    type = Comment
  }

  if (isVNode(type)) { // 如果是VNode,直接clone,这里就是通过type的__v_isVNode属性判断的
    // 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)) { // class组件
    type = type.__vccOpts
  }

  // class & style normalization.
  if (props) {
    // ...
  }

  // 给组件类型增加一个编码标示
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT // 1 dom element
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE //128 suspense vue3中新增的组件
      : isTeleport(type)
        ? ShapeFlags.TELEPORT // 64 teleport 也是vue3中新增
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT // 4 状态组件
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT // 2 函数组件
            : 0
  // ...
  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,
    shapeFlag
    // ...
  }

  /** 标准化子节点
    * 这里会给不同类型的children编码标示type 8: 文本; 16:数组; 32:slots;同时也转成对应类型。
    * 同时会因children类型不同,修改VNode的 shapeFlag,为之后挂载使用
    **/
  normalizeChildren(vnode, children)

  // normalize suspense children
  //...
  
  return vnode
}

Давайте посмотрим на процесс выполнения приведенного выше кода на этом примере.

  1. Определите, является ли это компонентом VNode, Class, и, если есть реквизиты, стандартизируйте преобразование класса и стиля.
  2. Определяем тип компонента и вычисляем оценку, получаем 4
  3. Создать виртуальный узел
  4. Стандартизируйте дочерние узлы, когда компонент приложения передается, дочерние узлы имеют значение null
  5. Вернуться к узлу

На данный момент мы получили VNode, созданный компонентом App:

VNode

визуализировать VNode

Давайте взглянем render(vnode, rootContainer), как визуализировать VNode.

В предыдущей статье мы также узналиrenderметод,baseCreateRenderer Передавая rendererOptions разных платформ, можно создавать средства визуализации для разных платформ.

render
// 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)
  }
  flushPostFlushCbs()
  // 存下 vnode于dom容器上
  container._vnode = vnode
}

Видно, что если входящий VNode пуст, а в текущем DOM-контейнере есть VNode, выполнить размонтирование, чтобы уничтожить компонент, в противном случае исправить входящий VNode. Тогда мы понимаемpatchреализация.

patch
  const patch: PatchFn = (
    n1, // n1 代表旧节点
    n2, // n2 代表新节点
    container,
    anchor = null,
    parentComponent = null,parentSuspense = null,isSVG = false,optimized = false
  ) => {
    // 如果有旧VNode,且不一样,umount销毁旧节点
    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
    // 先通过type来判断选择处理方法
    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:
        // 碎片化,这也是Vue3新支持的多根节点
        processFragment(/** ... **/)
        break
      default:
        // 如果type都不满足,使用shapeFlag 编码判断
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // dom元素
          processElement(/** ... **/)
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // 组件本次初次渲染会走到这里
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
          )
          // 之后都是Vue3 里面新增两种组件
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          //
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
        }
    }

    // set ref
    if (ref != null && parentComponent) {
      setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
    }
  }

фактическиpatchНаиболее важной логикой является выбор того, как поступать с компонентами через type и shapeFlag vnode.

Так как мы рендерим в первый раз, n1 пуст, и создается компонент AppVNodeизshapeFlagна 4ShapeFlags.STATEFUL_COMPONENT, так пойдет наShapeFlags.COMPONENTусловие, выполнитьprocessComponentметод. Тогда взгляните на этот метод.

processComponent
  const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
    if (n1 == null) {
    // 如果没有旧节点
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { // 512
      // 如果是 keep-alive 组件
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        // 执行挂载组件
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      // 如果n1 n2 都有则执行更新
      updateComponent(n1, n2, optimized)
    }
  }

Основная логика этого метода заключается в монтировании компонентов по наличию новых и старых узлов.mountComponent, ещеupdateComponentОбновление компонентов.

Давайте посмотрим на выполнение этого начального рендеринга.mountComponent

mountComponent
  const mountComponent: MountComponentFn = (
    initialVNode,   // 初始VNode 也就是App组件生成的VNode
    container,  // #app Dom容器
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized  
  ) => {
    // 创建组件实例
    const instance: ComponentInternalInstance = (initialVNode.component =    createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ))


    // inject renderer internals for keepAlive
    if (isKeepAlive(initialVNode)) {
      ;(instance.ctx as KeepAliveContext).renderer = internals
    }
    
    // 设置实例 初始化 props,slots 还有Vue3新增的composition API
    setupComponent(instance)
    
    // ...

    // effect 上一篇说到的副作用函数
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
  }

Основная логика монтажа компонента VNode такова:createComponentInstanceСоздание компонентовinstanceпример,setupComponentустановить компоненты,setupRenderEffectВыполнение функции рендеринга с побочными эффектами.

createComponentInstanceГлавное создать и вернутьinstanceпример, посмотримinstanceНа что это похоже.


  const instance: ComponentInternalInstance = {
    uid: uid++,
    vnode,
    type,
    parent,
    appContext,
    root: null!, // to be immediately set
    next: null,
    subTree: null!, // will be set synchronously right after creation
    update: null!, // will be set synchronously right after creation
    render: null,
    proxy: null,
    withProxy: null,
    effects: null,
    provides: parent ? parent.provides : Object.create(appContext.provides),
    accessCache: null!,
    renderCache: [],

    // local resovled assets
    components: null,
    directives: null,

    // resolved props and emits options
    // 

    // emit
    emit: null as any, // to be set immediately
    emitted: null,

    // state
    ctx: EMPTY_OBJ,
    data: EMPTY_OBJ,
    props: EMPTY_OBJ,
    // ...

    // suspense related
    // ...

    // lifecycle hooks
    // 以下是 组件生命周期相关的属性
    isMounted: false,
    isUnmounted: false,
    isDeactivated: false,
    bc: null, // beforeCreate
    c: null, // created
    // ...
  }

instanceЕсть много атрибутов объекта, которые будут использоваться в определенных сценариях, а позжеsetupComponentМетод также заключается в установке инициализацииinstanceсвойства, такие как инициализацияprops , slotsСуществует также функция настройки, которая запускает новый Vue3.

Поскольку он включает в себя новый API композиции и функцию настройки в Vue3, вы можете вырыть дыру, чтобы изучить этот контент отдельно.

После того, как экземпляр создан и установлен, последним шагом является установка и запуск функции побочного эффекта рендеринга.setupRenderEffect.

setupRenderEffect
  const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 创建响应式的副作用render函数
    instance.update = effect(function componentEffect() {
      if (!instance.isMounted) {
        let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode 
        const { bm, m, parent } = instance // 生命周期, beforemounted , mounted

        // bm 生命周期 及 hook 执行
        if (bm) {
          invokeArrayFns(bm)
        }
        // ..
        
       // 渲染组件生成 subTree VNode
       const subTree = (instance.subTree = renderComponentRoot(instance))

        if (el && hydrateNode) {
          // ...
        } else {
          // 把 subTree 挂载到Dom容器中
          patch(
            null,
            subTree,
            container,
            anchor,
            instance,
            parentSuspense,
            isSVG
          )

          initialVNode.el = subTree.el
        }
        
        // 生命周期 mounted hook 执行
        if (m) {
          queuePostRenderEffect(m, parentSuspense)
        }
        // ...
        
        instance.isMounted = true
      } else {
        // updateComponent
        // This is triggered by mutation of component's own state (next: null)
        // OR parent calling processComponent (next: VNode)

      }
    },  prodEffectOptions)
  }

Ознакомьтесь с содержанием предыдущей статьиeffectФункция не должна быть всем незнакома, запускайтеcomponentEffectИнициировать сбор зависимостей, собрать эту функцию эффекта и повторно выполнить ее при изменении данных компонента.effectв функцииcomponentEffectметод.

componentEffectОсновная логика состоит в том, чтобы сгенерировать VNode поддерева, а затем смонтировать поддерево.

renderComponentRoot
export function renderComponentRoot(
  instance: ComponentInternalInstance
): VNode {
  const {
    type: Component,
    vnode,
    proxy,
    withProxy,
    props,
    propsOptions: [propsOptions],
    slots,
    attrs,
    emit,
    render,  // 这里render 是 .vue 编译后的render函数
    renderCache,
    data,
    setupState,
    ctx
  } = instance

  let result
  currentRenderingInstance = instance
  
  try {
    let fallthroughAttrs
    if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
      const proxyToUse = withProxy || proxy
      // 本次例子中 这里会循环创建 Helloworld, p标签 VNode
      result = normalizeVNode(
        render!.call(
          proxyToUse,
          proxyToUse!,
          renderCache,
          props,
          setupState,
          data,
          ctx
        )
      )
      fallthroughAttrs = attrs
    } else {
      // functional
  } catch (err) {
    // ...
  }
  currentRenderingInstance = null

  return result
}

subTreeчто это такое? Например, первый пример компонента приложения: initialVNode,subTreeЭто виртуальный узел, сгенерированный структурой в шаблоне компонента приложения.childrenсобственность HelloWorldкомпонент VNode,pМетка VNode.

Компоненты приложенияinitialVNodeизchidrenвнутри, согласноHelloWorld Метка, сгенерированная VNode, дляHelloWorld Внутренняя структура DOM компонента initialVNode, а VNode, сгенерированный его внутренней структурой DOM,subTree.

На следующем рисунке показана скомпилированная функция рендеринга HelloWorld.vue в примере.

Это поддерево приложения

Вы можете видеть, что в нем есть детиHelloworld, pМетка VNode.

назадsetupRenderEffectметод созданияsubTreeПосле этого мы возвращаемся к нашему предыдущему процессу исправления, чтобы определить, что делать с входящим VNode, и так далее, и так далее, пока мы не исправим реальные элементы DOM, комментарии и другие VNode.

Не знаю, заметили ли вы, что в шаблоне App.vue в стартовом примере отсутствует корневой узел, что такжеVue3Medium Новая поддерживаемая функция определенно нужна в Vue2.divПучокHelloWorld, pзавернутые в этикетки.

Итак, в нашем примере компонент APPsubTreeанализируется как typeдляSymbol(Fragment)Vузел.

назадpatchметод см.processFragment

  const processFragment = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
    // 没有根节点,要确认分配在何处
    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!
    // ...
    if (n1 == null) {
      hostInsert(fragmentStartAnchor, container, anchor)
      hostInsert(fragmentEndAnchor, container, anchor)
      // 走到这里children一定会是数组
      mountChildren(
        n2.children as VNodeArrayChildren,
        container,
        fragmentEndAnchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    } else {}
  }

надhostCreateText , hostInsertВсе создано, как мы сказали в предыдущем постеrenderвходящийrendererOptions, который содержит DOM API браузера, props. НапримерhostCreateTextНа самом деле этоdocument.createTextNode,hostInsertто естьparent.insertBefore(*child*, *anchor* || null).

processFragmentПосле того, как место определено,mountChildrenиметь дело сchildrenМассив VNodes.

mountChildren
const mountChildren: MountChildrenFn = (
    children,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized,
    start = 0
  ) => {
    for (let i = start; i < children.length; i++) {
      const child = (children[i] = optimized
        ? cloneIfMounted(children[i] as VNode)
        : normalizeVNode(children[i]))
      // patch每一个VNode
      patch(
        null,
        child,
        container, 
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    }
  }

можно увидетьmountChildrenпройдетchildren, patch каждый VNode к текущемуcontainerВниз.

назад сноваpatch, Тогда давайте посмотрим, как это обрабатывается, если это узел DOM VNode.

  const processElement = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    } else {
      // 
    }
  }

Процесс обработки компонентов почти такой же, как выполнять монтирование или обновление в зависимости от того, есть старые узлы или нет.

mountElement
  const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) => {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const {
      type,
      props,
      shapeFlag,
      transition,
      scopeId,
      patchFlag,
      dirs
    } = vnode
    // ...
      // 调用传入的API创建DOM元素
      el = vnode.el = hostCreateElement(
        vnode.type as string,
        isSVG,
        props && props.is
      )

      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 8
        // 如果是子节点文本 创建文本
        hostSetElementText(el, vnode.children as string)
      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 16
        // 如果是数组,回到mountChildren遍历继续patch子节点
        // 注意这里传入的 container 已经是刚刚创建的 el DOM元素,这样就创建了父子关系
        mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type !== 'foreignObject',
          optimized || !!vnode.dynamicChildren
        )
      }

      if (dirs) {
        // 调用指令相关的生命周期处理
        invokeDirectiveHook(vnode, null, parentComponent, 'created')
      }
      // 如果有DOM的 props,例如原生的class style,自定义的prop等
      if (props) {
        for (const key in props) {
          if (!isReservedProp(key)) {
            hostPatchProp(
              el,
              key,
              null,
              props[key],
              isSVG,
              vnode.children as VNode[],
              parentComponent,
              parentSuspense,
              unmountChildren
            )
          }
        }
        if ((vnodeHook = props.onVnodeBeforeMount)) {
          invokeVNodeHook(vnodeHook, parentComponent, vnode)
        }
      }
      // ...

    
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
    }

    /** 把创建的el DOM挂载到 contanier容器上
    *   初次渲染container是 #app 容器,但是之后就是对应的父级DOM容器了
    **/
    hostInsert(el, container, anchor)
    
    // ...
  }

Видно, что основная логика обработки и монтирования DOM-узлов заключается в первом вызовеhostCreateElementсоздать ДОМ,hostCreateElementНа самом деле он вызывает браузерdocument.createElement. Затем определите, является ли дочерний узел обработки текстом или массивом. Затем обработайте собственные или пользовательские свойства модели DOM. последний звонокinsertМонтируется в DOM-контейнер.

кHelloWorldВнутри компонентаdivНапример, егоchildrenпросто абзац, который мы прошлиpropвходящий текст, поэтому звонитеhostSetElementText: el.textContent = *text*Просто вставьте текст.

У некоторых могут быть сомненияdivКак VNode of shapeFlag будет 9, помнитеcreateVNodeвнутри методаnormalizeChildrenдействовать? Он изменяет значение shapeFlag в зависимости от того, является ли тип дочерних элементов массивом, текстом или слотом.

резюме

Глядя на процесс рендеринга через код, вы не чувствуете себя очень запутанным, вы можете использовать блок-схему, чтобы понять это.

конец

Спасибо за прочтение, на днях智云健康大前端团队участие掘金人气团队评选活动. Если тебе хорошо, то приходиГолосуйте за насБар!

Всего сегодня можно отдать 12 голосов: 4 в Интернете, 4 в приложении и 4 на акции. Спасибо за вашу поддержку, мы создадим больше технических статей в 2021 году~~~

Ваша поддержка - наша самая большая мотивация~