浅曦Vue源码-32-挂载阶段-$mount-动态组件&自定义组件(21)

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战」。

一、前情回顾 & 背景

上一篇小作文提前介绍了 Vue 在编译时对 <slot> 这个占位符标签的一个处理,编译 slot 标签最终得到一个叫做 _t 的渲染函数的运行时帮助函数的调用:_t(slotName, children, attrs, bind)

其中 slotName 即为具名插槽 slot 标签上的 name 属性值,如果是匿名插槽,则 slotName"default"

childrenslot 标签的子元素;虽然插槽最终使用时不会展示,但是当组件被引用时没有传递插槽内容时,slot 的子元素仍然会被渲染用以占位提示。

我们前面说了以下几种情况的渲染函数:

  1. 静态根节点的渲染函数,提升到 staticRenderFns 数组;
  2. 带有 v-modelinput 渲染函数,主要处理不同元素、input 动态绑定 type 时不同的事件处理类型和绑定值的类型;
  3. 带有 v-if/v-else-if/v-else 的条件渲染,根据 el.ifConditons 处理成套娃式的三元表达式;
  4. 带有 v-for 的列表渲染,根据 el.forel.alias 处理成 _l(el.for, function (it1,it2) {return _c(...)}) 的调用;
  5. 处理 标签的渲染,如果 el.tag,获取其 slotName 处理成 _t(slotName,children...)

除了动态组件(<component :is="someCompName" />)、自定义的组件渲染函数我们没有聊,常见的场景我们已经说的差不多了,今天我们着重看下动态组价、自定义组件的渲染函数;

二、自定义组件的处理

在我们的 test.html 中有一个 <some-com></some-com> 自定义组件,我们以他为例子看下自定义组件的渲染函数;

2.1 例子 test.html

<div id="app">
 <some-com :some-key="forProp">
    <div>someCom 的插槽内容</div>
 </some-com>
</div>
复制代码

2.2 genElement 处理自定义组件

这里要说的是,在根实例的渲染函数中,处理自定义组件和处理原生的 HTML 元素的差异并不大,都是在 genElementelse 中处理的,包括后面要说的动态组件也是在这里处理的;

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.parent) {
  } else {
    // 这个 else 处理:动态组件、普通元素、自定义组件

    let code
    if (el.component) {
      // el.component 标识的不是普通组件,是动态组件 <component :is="someComp" /> 这种
      code = genComponent(el.component, el, state)
    } else {
      let data
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        // 非普通元素或者带有 v-pre 指令的组件走这里,处理节点的所有属性,返回一个 JSON 字符串,
        // 比如:'{ key: xx, ref: xx, ....}'
        data = genData(el, state)
      }

      // 处理子节点,得到所有的子节点字符串格式的代码组成的数组,格式:
      // `['_c(tag, data, children)', ....], normalizationType`
      const children = el.inlineTemplate ? null : genChildren(el, state, true)

      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }

    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}
复制代码

先上一站图,可以看下此时 <some-com></some-com> 组件 的 ast 对象如图:

image.png 从图上可以看出 genElement 进入到了 else,并且 el的标签名el.tagsome-com,从例子中可以知道,some-comp 组件引用时有传入插槽内容进行分发:

someComp插槽内容
,所以 el.children` 就是这个插槽内容了;

image.png

插槽内容也是作为正常的子节点进行渲染的,上面的插槽的渲染函数如下:

"[_c('div',[_v(\"someCom 的插槽内容\")])]"
复制代码

综合上面的结果,我们就得到了 some-com 组件的渲染函数代码:

"_c(
  'some-com',
  { attrs: {\"some-key\":forProp} },
  [_c(
     'div',
     [_v(\"someCom 的插槽内容\")]
   )
  ]
)"
复制代码

三、genElement 处理动态组件

你日常开发的时候总是会遇到使用动态组件的场景,这个东西说实话是个好东西,给了开发人员巨大的想象和发挥空间,服务端甚至可以返回一个字符串,就可以对应一个具体的视图;

不过这也使得它看起来很神秘,今天咱们一探究竟吧;

3.1 test.html 的示例

<div id="app">
 <component is="someCom">
  <div>componet 的插槽</div>
 </component>
</div>
复制代码

3.2 genElement 中的 genComponent

genElement 处理动态组件的逻辑同样是在 else 中处理的,判断 el.component 有值时执行的逻辑就是处理动态组件的部分

export function genElement (): string {
  if (el.parent) {
  } else {
    let code
    if (el.component) {
      // el.component 标识的不是普通组件,是动态组件 <component :is="someComp" /> 这种
      // el.componet 是动态组件绑定的 is 属性的值,是个组件名
      code = genComponent(el.component, el, state)
    } else {
   
    return code
  }
}
复制代码

3.3 genCompoent

方法位置:src/compiler/codegen/index.js -> genComponent

方法参数:

  1. componentName: 组件名,即 is 属性绑定的值;
  2. el: ast 节点对象;
  3. state: CodegenState 实例;

方法作用:生成 <component></component> 动态组件的渲染函数,具体工作如下:

  1. 处理 component 的子元素,获取子元素的 render 函数代码组成的数组;
  2. 拼接当前动态组件指向的 componentName 组件的渲染函数代码,形如: _c(compName, data, children)
function genComponent (
  componentName: string,
  el: ASTElement,
  state: CodegenState
): string {
  const children = el.inlineTemplate ? null : genChildren(el, state, true)
  return `_c(${componentName},${genData(el, state)}${
    children ? `,${children}` : ''
  })`
}
复制代码

四、总结

本篇小作文讨论了另外两个常用的功能对应的渲染函数:自定义组件、动态组件;

二者的处理都是处理成 _c(componentName, data, children) 的调用形式;

到这里多少有点失望,我们发现这和渲染个普通的 div 并无二致,说好的自定义组件呢?还有生命周期呢?

先别急,前面我说过现在我们正在处理根实例的渲染函数,这个自定义组件是个子实例,它现在没有进行初始化。那么什么时候才能到这个子实例的初始化呢?此时此刻尚在根实例编译阶段,只有当根实例的编译阶段结束,得到根实例的渲染函数,接下来创建根实例渲染 watcher 就会调用根实例的渲染函数。

根实例的渲染函数执行,也就是上面那一串串 _c/_l/_t/_s/_v... 执行的时候,这些帮助函数的执行,如果遇到是自定义组件,就会接着走重新创建一个新的 Vue 实例,而创建一个新的 Vue 实例就会重新走一遍 Vue_init 逻辑包含处理数据响应式、编译模板生成 ast 得到子实例 render 函数,接着创建子实例渲染 wathcer 执行子实例的 render 函数得到 DOM 然后完成挂载;

Guess you like

Origin juejin.im/post/7067688499868073992