【源码分析】@vue/runtime-dom/src/apiCustomElement.ts 解析
源码来源于
@vue/runtime-dom/src/apiCustomElement.ts
其他参考@vue/runtime-core/src/apiDefineComponent.ts
、@vue/runtime-core/src/componentProps.ts
、@vue/shared/src/general.ts
、
export type VueElementConstructor<P = {}> = {new (initialProps?: Record<string, any>): VueElement & P
}
这段代码定义了一个可导出的泛型类型VueElementConstructor
,用于描述能创建VueElement
实例的构造函数类型,该构造函数可接收可选的任意属性对象作为初始参数,返回的实例同时具备VueElement
类型和泛型参数P
(默认空对象)所指定的类型特性,主要作用是在TypeScript中约束和描述Vue元素构造函数的参数和返回值类型,确保类型安全。
export interface CustomElementOptions {styles?: string[]shadowRoot?: booleannonce?: stringconfigureApp?: (app: App) => void
}
这段代码定义了一个可导出的接口CustomElementOptions
,用于规范自定义元素(通常是Vue自定义元素)的配置选项结构,包含可选的样式数组(styles
)、是否启用影子DOM(shadowRoot
)、安全随机数(nonce
),以及用于配置Vue应用实例的函数(configureApp
),核心作用是为自定义元素的创建提供统一的类型约束,确保配置项的类型安全和使用规范性。
defineCustomElement 重载
// overload 1: direct setup function
export function defineCustomElement<Props, RawBindings = object>(setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> &CustomElementOptions & {props?: (keyof Props)[]},
): VueElementConstructor<Props>
这段代码定义了defineCustomElement
函数的第一个重载形式,用于接收一个setup
函数和包含组件名称、属性继承、事件声明等的配置选项,返回能创建对应Vue元素实例的构造函数,主要作用是提供一种通过setup
函数定义Vue自定义元素的方式,并通过TypeScript泛型确保属性等类型的一致性。
export function defineCustomElement<Props, RawBindings = object>(setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> &CustomElementOptions & {props?: ComponentObjectPropsOptions<Props>},
): VueElementConstructor<Props>
这段代码定义了defineCustomElement
函数的一种重载形式,用于接收setup
函数作为核心逻辑入口来创建Vue自定义元素,setup
函数接收props
和上下文对象并返回绑定数据或渲染函数;第二个参数为可选配置,可包含组件名称、属性继承、事件声明等组件选项及自定义元素配置,且支持以对象形式定义props
;最终返回对应Props
类型的Vue自定义元素构造函数,作用是提供一种基于setup
函数和对象式属性配置定义类型安全的自定义元素的方式。
// overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement<// propsRuntimePropsOptions extendsComponentObjectPropsOptions = ComponentObjectPropsOptions,PropsKeys extends string = string,// emitsRuntimeEmitsOptions extends EmitsOptions = {},EmitsKeys extends string = string,// other optionsData = {},SetupBindings = {},Computed extends ComputedOptions = {},Methods extends MethodOptions = {},Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,Extends extends ComponentOptionsMixin = ComponentOptionsMixin,InjectOptions extends ComponentInjectOptions = {},InjectKeys extends string = string,Slots extends SlotsType = {},LocalComponents extends Record<string, Component> = {},Directives extends Record<string, Directive> = {},Exposed extends string = string,Provide extends ComponentProvideOptions = ComponentProvideOptions,// resolved typesInferredProps = string extends PropsKeys? ComponentObjectPropsOptions extends RuntimePropsOptions? {}: ExtractPropTypes<RuntimePropsOptions>: { [key in PropsKeys]?: any },ResolvedProps = InferredProps & EmitsToProps<RuntimeEmitsOptions>,
>(options: CustomElementOptions & {props?: (RuntimePropsOptions & ThisType<void>) | PropsKeys[]} & ComponentOptionsBase<ResolvedProps,SetupBindings,Data,Computed,Methods,Mixin,Extends,RuntimeEmitsOptions,EmitsKeys,{}, // DefaultsInjectOptions,InjectKeys,Slots,LocalComponents,Directives,Exposed,Provide> &ThisType<CreateComponentPublicInstanceWithMixins<Readonly<ResolvedProps>,SetupBindings,Data,Computed,Methods,Mixin,Extends,RuntimeEmitsOptions,EmitsKeys,{},false,InjectOptions,Slots,LocalComponents,Directives,Exposed>>,extraOptions?: CustomElementOptions,
): VueElementConstructor<ResolvedProps>
这段代码定义了defineCustomElement
函数的第二种重载形式,支持通过包含自定义元素配置与组件完整选项(如props、emits、data等)的选项对象来定义Vue自定义元素,通过复杂泛型逻辑从选项中自动推断属性(props)类型,最终返回对应解析后属性类型的Vue自定义元素构造函数,作用是提供一种基于完整选项对象定义自定义元素的方式,同时确保类型推断的准确性和对组件各类选项的全面支持。
// overload 3: defining a custom element from the returned value of
// `defineComponent`
export function defineCustomElement<// this should be `ComponentPublicInstanceConstructor` but that type is not exportedT extends { new (...args: any[]): ComponentPublicInstance<any> },
>(options: T,extraOptions?: CustomElementOptions,
): VueElementConstructor<T extends DefineComponent<infer P, any, any, any> ? P : unknown
>
这段代码是defineCustomElement
函数的第三种重载形式,用于接收defineComponent
返回的组件构造函数,结合可选的自定义元素配置,通过泛型推断组件属性类型,最终返回对应属性类型的Vue自定义元素构造函数,作用是支持将已用defineComponent
定义的组件直接转换为自定义元素,实现组件复用。
/*! #__NO_SIDE_EFFECTS__ */
export function defineCustomElement(options: any,extraOptions?: ComponentOptions,/*** @internal*/_createApp?: CreateAppFunction<Element>,
): VueElementConstructor {const Comp = defineComponent(options, extraOptions) as anyif (isPlainObject(Comp)) extend(Comp, extraOptions)class VueCustomElement extends VueElement {static def = Compconstructor(initialProps?: Record<string, any>) {super(Comp, initialProps, _createApp)}}return VueCustomElement
}
这段代码是defineCustomElement
函数的核心实现,通过调用defineComponent
处理传入的组件选项及额外配置,合并为标准组件对象,再创建继承自VueElement
的自定义元素类,该类存储组件定义并在构造时初始化,最终返回可用于注册为Web Components的Vue自定义元素构造函数,实现将Vue组件转换为原生自定义元素。
VueElement
export class VueElementextends BaseClassimplements ComponentCustomElementInterface
{_isVueCE = true/*** @internal*/_instance: ComponentInternalInstance | null = null/*** @internal*/_app: App | null = null/*** @internal*/_root: Element | ShadowRoot/*** @internal*/_nonce: string | undefined = this._def.nonce/*** @internal*/_teleportTarget?: HTMLElementprivate _connected = falseprivate _resolved = falseprivate _numberProps: Record<string, true> | null = nullprivate _styleChildren = new WeakSet()private _pendingResolve: Promise<void> | undefinedprivate _parent: VueElement | undefined/*** dev only*/private _styles?: HTMLStyleElement[]/*** dev only*/private _childStyles?: Map<string, HTMLStyleElement[]>private _ob?: MutationObserver | null = nullprivate _slots?: Record<string, Node[]>constructor(/*** Component def - note this may be an AsyncWrapper, and this._def will* be overwritten by the inner component when resolved.*/private _def: InnerComponentDef,private _props: Record<string, any> = {},private _createApp: CreateAppFunction<Element> = createApp,) {super()if (this.shadowRoot && _createApp !== createApp) {this._root = this.shadowRoot} else {if (__DEV__ && this.shadowRoot) {warn(`Custom element has pre-rendered declarative shadow root but is not ` +`defined as hydratable. Use \`defineSSRCustomElement\`.`,)}if (_def.shadowRoot !== false) {this.attachShadow({ mode: 'open' })this._root = this.shadowRoot!} else {this._root = this}}}connectedCallback(): void {// avoid resolving component if it's not connectedif (!this.isConnected) return// avoid re-parsing slots if already resolvedif (!this.shadowRoot && !this._resolved) {this._parseSlots()}this._connected = true// locate nearest Vue custom element parent for provide/injectlet parent: Node | null = thiswhile ((parent = parent && (parent.parentNode || (parent as ShadowRoot).host))) {if (parent instanceof VueElement) {this._parent = parentbreak}}if (!this._instance) {if (this._resolved) {this._mount(this._def)} else {if (parent && parent._pendingResolve) {this._pendingResolve = parent._pendingResolve.then(() => {this._pendingResolve = undefinedthis._resolveDef()})} else {this._resolveDef()}}}}private _setParent(parent = this._parent) {if (parent) {this._instance!.parent = parent._instancethis._inheritParentContext(parent)}}private _inheritParentContext(parent = this._parent) {// #13212, the provides object of the app context must inherit the provides// object from the parent element so we can inject values from both placesif (parent && this._app) {Object.setPrototypeOf(this._app._context.provides,parent._instance!.provides,)}}disconnectedCallback(): void {this._connected = falsenextTick(() => {if (!this._connected) {if (this._ob) {this._ob.disconnect()this._ob = null}// unmountthis._app && this._app.unmount()if (this._instance) this._instance.ce = undefinedthis._app = this._instance = null}})}/*** resolve inner component definition (handle possible async component)*/private _resolveDef() {if (this._pendingResolve) {return}// set initial attrsfor (let i = 0; i < this.attributes.length; i++) {this._setAttr(this.attributes[i].name)}// watch future attr changesthis._ob = new MutationObserver(mutations => {for (const m of mutations) {this._setAttr(m.attributeName!)}})this._ob.observe(this, { attributes: true })const resolve = (def: InnerComponentDef, isAsync = false) => {this._resolved = truethis._pendingResolve = undefinedconst { props, styles } = def// cast Number-type props set before resolvelet numberPropsif (props && !isArray(props)) {for (const key in props) {const opt = props[key]if (opt === Number || (opt && opt.type === Number)) {if (key in this._props) {this._props[key] = toNumber(this._props[key])};(numberProps || (numberProps = Object.create(null)))[camelize(key)] = true}}}this._numberProps = numberPropsthis._resolveProps(def)// apply CSSif (this.shadowRoot) {this._applyStyles(styles)} else if (__DEV__ && styles) {warn('Custom element style injection is not supported when using ' +'shadowRoot: false',)}// initial mountthis._mount(def)}const asyncDef = (this._def as ComponentOptions).__asyncLoaderif (asyncDef) {this._pendingResolve = asyncDef().then((def: InnerComponentDef) => {def.configureApp = this._def.configureAppresolve((this._def = def), true)})} else {resolve(this._def)}}private _mount(def: InnerComponentDef) {if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && !def.name) {// @ts-expect-errordef.name = 'VueElement'}this._app = this._createApp(def)// inherit before configureApp to detect context overwritesthis._inheritParentContext()if (def.configureApp) {def.configureApp(this._app)}this._app._ceVNode = this._createVNode()this._app.mount(this._root)// apply expose after mountconst exposed = this._instance && this._instance.exposedif (!exposed) returnfor (const key in exposed) {if (!hasOwn(this, key)) {// exposed properties are readonlyObject.defineProperty(this, key, {// unwrap ref to be consistent with public instance behaviorget: () => unref(exposed[key]),})} else if (__DEV__) {warn(`Exposed property "${key}" already exists on custom element.`)}}}private _resolveProps(def: InnerComponentDef) {const { props } = defconst declaredPropKeys = isArray(props) ? props : Object.keys(props || {})// check if there are props set pre-upgrade or connectfor (const key of Object.keys(this)) {if (key[0] !== '_' && declaredPropKeys.includes(key)) {this._setProp(key, this[key as keyof this])}}// defining getter/setters on prototypefor (const key of declaredPropKeys.map(camelize)) {Object.defineProperty(this, key, {get() {return this._getProp(key)},set(val) {this._setProp(key, val, true, true)},})}}protected _setAttr(key: string): void {if (key.startsWith('data-v-')) returnconst has = this.hasAttribute(key)let value = has ? this.getAttribute(key) : REMOVALconst camelKey = camelize(key)if (has && this._numberProps && this._numberProps[camelKey]) {value = toNumber(value)}this._setProp(camelKey, value, false, true)}/*** @internal*/protected _getProp(key: string): any {return this._props[key]}/*** @internal*/_setProp(key: string,val: any,shouldReflect = true,shouldUpdate = false,): void {if (val !== this._props[key]) {if (val === REMOVAL) {delete this._props[key]} else {this._props[key] = val// support set key on ceVNodeif (key === 'key' && this._app) {this._app._ceVNode!.key = val}}if (shouldUpdate && this._instance) {this._update()}// reflectif (shouldReflect) {const ob = this._obob && ob.disconnect()if (val === true) {this.setAttribute(hyphenate(key), '')} else if (typeof val === 'string' || typeof val === 'number') {this.setAttribute(hyphenate(key), val + '')} else if (!val) {this.removeAttribute(hyphenate(key))}ob && ob.observe(this, { attributes: true })}}}private _update() {const vnode = this._createVNode()if (this._app) vnode.appContext = this._app._contextrender(vnode, this._root)}private _createVNode(): VNode<any, any> {const baseProps: VNodeProps = {}if (!this.shadowRoot) {baseProps.onVnodeMounted = baseProps.onVnodeUpdated =this._renderSlots.bind(this)}const vnode = createVNode(this._def, extend(baseProps, this._props))if (!this._instance) {vnode.ce = instance => {this._instance = instanceinstance.ce = thisinstance.isCE = true // for vue-i18n backwards compat// HMRif (__DEV__) {instance.ceReload = newStyles => {// always reset stylesif (this._styles) {this._styles.forEach(s => this._root.removeChild(s))this._styles.length = 0}this._applyStyles(newStyles)this._instance = nullthis._update()}}const dispatch = (event: string, args: any[]) => {this.dispatchEvent(new CustomEvent(event,isPlainObject(args[0])? extend({ detail: args }, args[0]): { detail: args },),)}// intercept emitinstance.emit = (event: string, ...args: any[]) => {// dispatch both the raw and hyphenated versions of an event// to match Vue behaviordispatch(event, args)if (hyphenate(event) !== event) {dispatch(hyphenate(event), args)}}this._setParent()}}return vnode}private _applyStyles(styles: string[] | undefined,owner?: ConcreteComponent,) {if (!styles) returnif (owner) {if (owner === this._def || this._styleChildren.has(owner)) {return}this._styleChildren.add(owner)}const nonce = this._noncefor (let i = styles.length - 1; i >= 0; i--) {const s = document.createElement('style')if (nonce) s.setAttribute('nonce', nonce)s.textContent = styles[i]this.shadowRoot!.prepend(s)// record for HMRif (__DEV__) {if (owner) {if (owner.__hmrId) {if (!this._childStyles) this._childStyles = new Map()let entry = this._childStyles.get(owner.__hmrId)if (!entry) {this._childStyles.set(owner.__hmrId, (entry = []))}entry.push(s)}} else {;(this._styles || (this._styles = [])).push(s)}}}}/*** Only called when shadowRoot is false*/private _parseSlots() {const slots: VueElement['_slots'] = (this._slots = {})let nwhile ((n = this.firstChild)) {const slotName =(n.nodeType === 1 && (n as Element).getAttribute('slot')) || 'default';(slots[slotName] || (slots[slotName] = [])).push(n)this.removeChild(n)}}/*** Only called when shadowRoot is false*/private _renderSlots() {const outlets = (this._teleportTarget || this).querySelectorAll('slot')const scopeId = this._instance!.type.__scopeIdfor (let i = 0; i < outlets.length; i++) {const o = outlets[i] as HTMLSlotElementconst slotName = o.getAttribute('name') || 'default'const content = this._slots![slotName]const parent = o.parentNode!if (content) {for (const n of content) {// for :slotted cssif (scopeId && n.nodeType === 1) {const id = scopeId + '-s'const walker = document.createTreeWalker(n, 1);(n as Element).setAttribute(id, '')let childwhile ((child = walker.nextNode())) {;(child as Element).setAttribute(id, '')}}parent.insertBefore(n, o)}} else {while (o.firstChild) parent.insertBefore(o.firstChild, o)}parent.removeChild(o)}}/*** @internal*/_injectChildStyle(comp: ConcreteComponent & CustomElementOptions): void {this._applyStyles(comp.styles, comp)}/*** @internal*/_removeChildStyle(comp: ConcreteComponent): void {if (__DEV__) {this._styleChildren.delete(comp)if (this._childStyles && comp.__hmrId) {// clear old stylesconst oldStyles = this._childStyles.get(comp.__hmrId)if (oldStyles) {oldStyles.forEach(s => this._root.removeChild(s))oldStyles.length = 0}}}}
}
整体内容与作用
VueElement
是 Vue 自定义元素(Web Components)的核心基类,继承自 BaseClass
并实现 ComponentCustomElementInterface
,负责将 Vue 组件逻辑与原生 Web Components 规范深度整合:管理组件内部实例(_instance
)、渲染根节点(_root
,ShadowRoot 或元素自身)、生命周期(连接/断开 DOM)、props 传递与同步、样式注入、插槽解析渲染,以及将 Vue 组件的 emit
事件转为原生 CustomEvent
,最终实现 Vue 组件向原生自定义元素的适配,支持跨框架复用。
各方法/构造函数的内容与作用
1. 构造函数 constructor(_def, _props?, _createApp?)
- 内容:初始化自定义元素基础状态,处理 Shadow DOM 配置:
- 接收组件定义(
_def
)、初始 props(_props
)和 App 创建函数(_createApp
); - 若元素已有预渲染的声明式 ShadowRoot 且使用自定义
_createApp
,直接将其设为_root
; - 若
_def.shadowRoot
不为false
,创建开放模式(open
)的 ShadowRoot 作为_root
,否则以元素自身为_root
; - 开发环境下,对已有 ShadowRoot 但非 hydration 场景给出警告。
- 接收组件定义(
- 作用:奠定自定义元素的渲染基础,确定渲染根节点,初始化核心配置。
2. 生命周期回调 connectedCallback()
- 内容:原生 Web Components 生命周期,当元素插入 DOM 时触发:
- 检查元素是否真的连接(
isConnected
),避免无效执行; - 未使用 ShadowRoot 且未解析组件时,解析插槽(
_parseSlots
); - 查找最近的父级
VueElement
(用于provide/inject
上下文继承); - 若未创建组件实例(
_instance
),已解析组件则直接挂载(_mount
),未解析则触发组件定义解析(_resolveDef
),父组件未解析时等待父解析完成。
- 检查元素是否真的连接(
- 作用:触发自定义元素的初始化流程,关联父组件上下文,启动组件解析与挂载。
3. 私有方法 _setParent(parent?)
- 内容:将当前自定义元素的组件实例(
_instance
)的parent
指向父VueElement
的组件实例。 - 作用:建立自定义元素间的组件实例层级,确保组件树关系正确(如父子组件通信、生命周期传递)。
4. 私有方法 _inheritParentContext(parent?)
- 内容:若存在父
VueElement
且当前已创建 App(_app
),将 App 上下文的provides
原型设为父组件实例的provides
。 - 作用:继承父组件的
provide
上下文,确保inject
API 在自定义元素层级间生效(如跨自定义元素注入值)。
5. 生命周期回调 disconnectedCallback()
- 内容:原生 Web Components 生命周期,当元素从 DOM 移除时触发:
- 标记
_connected
为false
,延迟(nextTick
)执行清理逻辑; - 断开 MutationObserver(停止监听属性变化);
- 卸载 App(
_app.unmount()
),销毁组件实例(_instance
),重置_app
和_instance
。
- 标记
- 作用:避免内存泄漏,清理自定义元素关联的资源(监听、组件实例、App)。
6. 私有方法 _resolveDef()
- 内容:解析组件定义(处理同步/异步组件),初始化 props 和样式:
- 监听自定义元素的属性变化(
MutationObserver
),属性变化时触发_setAttr
; - 处理异步组件:若组件定义含
__asyncLoader
,加载完成后更新_def
并继续解析; - 同步组件:转换 Number 类型 props 的初始值,解析 props 并定义 getter/setter(
_resolveProps
),注入组件样式(_applyStyles
)。
- 监听自定义元素的属性变化(
- 作用:完成组件定义的解析(尤其是异步组件),同步初始属性,为组件挂载做准备。
7. 私有方法 _mount(def)
- 内容:创建 Vue App 并挂载组件到渲染根(
_root
):- 开发/生产调试模式下,为组件定义补充默认名称(
VueElement
); - 调用
_createApp
创建 App,继承父上下文(_inheritParentContext
),执行组件的configureApp
配置; - 创建组件 VNode(
_createVNode
)并挂载到_root
; - 组件暴露(
exposed
)的属性,在自定义元素上定义只读 getter(解包ref
,与 Vue 实例行为一致)。
- 开发/生产调试模式下,为组件定义补充默认名称(
- 作用:启动 Vue 组件的渲染流程,将组件挂载到自定义元素的渲染根,暴露组件属性供外部访问。
8. 私有方法 _resolveProps(def)
- 内容:解析组件声明的 props,定义自定义元素的 props 访问器:
- 提取组件声明的 props 键(数组/对象形式);
- 同步自定义元素自身已有的 props 值到
_props
; - 为每个 props 键(转为驼峰)定义 getter(调用
_getProp
)和 setter(调用_setProp
)。
- 作用:实现自定义元素的 props 与 Vue 组件 props 的双向绑定,确保外部修改自定义元素属性时同步到组件内部。
9. 保护方法 _setAttr(key)
- 内容:处理自定义元素的属性变化(排除
data-v-
类属性):- 将属性名转为驼峰(
camelize
),判断是否为 Number 类型 props,若则转换属性值为 Number; - 调用
_setProp
更新对应的 props,不反射属性到 DOM(shouldReflect=false
)。
- 将属性名转为驼峰(
- 作用:将原生属性变化同步到组件 props,处理属性类型转换(如字符串转 Number)。
10. 保护方法 _getProp(key)
- 内容:返回
_props
中指定键的值。 - 作用:作为 props getter 的底层实现,提供组件 props 的读取能力。
11. 内部方法 _setProp(key, val, shouldReflect?, shouldUpdate?)
- 内容:更新 props 并同步到组件/DOM:
- 若值变化,更新
_props
(值为REMOVAL
时删除键),key
为key
时同步 VNode 的key
; shouldUpdate=true
时,触发组件重新渲染(_update
);shouldReflect=true
时,将 props 变化反射到自定义元素的 DOM 属性(驼峰转连字符,布尔值特殊处理)。
- 若值变化,更新
- 作用:实现 props 的更新逻辑,同步组件内部状态与外部 DOM 属性,触发组件重渲染。
12. 私有方法 _update()
- 内容:创建新的组件 VNode,继承 App 上下文,重新渲染到
_root
(调用render
)。 - 作用:触发组件的重新渲染,响应 props 变化或其他需要更新视图的场景。
13. 私有方法 _createVNode()
- 内容:创建组件对应的 VNode,配置组件实例回调:
- 非 ShadowRoot 场景,为 VNode 绑定
onVnodeMounted
/onVnodeUpdated
(触发_renderSlots
); - 首次创建时,在 VNode 的
ce
回调中初始化组件实例(_instance
),拦截emit
转为原生CustomEvent
(同时触发原始名和连字符名事件); - 处理开发环境 HMR:定义
ceReload
方法,支持样式重载和组件重新渲染。
- 非 ShadowRoot 场景,为 VNode 绑定
- 作用:创建 Vue 组件的 VNode 载体,关联自定义元素与组件实例,适配
emit
事件为原生规范。
14. 私有方法 _applyStyles(styles, owner?)
- 内容:将组件样式注入到 ShadowRoot(非 ShadowRoot 场景开发环境警告):
- 为每个样式字符串创建
<style>
标签,添加nonce
(若有),插入到 ShadowRoot 顶部; - 开发环境下,记录样式(自身样式存
_styles
,子组件样式存_childStyles
),用于 HMR 清理。
- 为每个样式字符串创建
- 作用:实现组件样式的隔离注入(依赖 ShadowRoot),支持样式 HMR。
15. 私有方法 _parseSlots()
- 内容:非 ShadowRoot 场景下,解析自定义元素的子节点为插槽:
- 遍历子节点,根据
slot
属性分类存储到_slots
(默认插槽存default
键),并从自定义元素中移除子节点(避免原生渲染)。
- 遍历子节点,根据
- 作用:为非 ShadowRoot 场景准备插槽内容,适配 Vue 的插槽逻辑。
16. 私有方法 _renderSlots()
- 内容:非 ShadowRoot 场景下,将解析的插槽内容渲染到
<slot>
出口:- 查找所有
<slot>
元素,根据name
属性匹配_slots
中的内容; - 插入插槽内容到
<slot>
位置,移除原生<slot>
元素; - 处理 scoped 样式:为插槽元素添加
scopeId-s
属性,确保:slotted
选择器生效。
- 查找所有
- 作用:在非 ShadowRoot 场景下实现 Vue 插槽的渲染逻辑,适配 scoped 样式。
17. 内部方法 _injectChildStyle(comp)
- 内容:调用
_applyStyles
注入子组件(comp
)的样式到当前自定义元素的 ShadowRoot。 - 作用:支持子组件样式在父自定义元素的 ShadowRoot 中隔离注入。
18. 内部方法 _removeChildStyle(comp)
- 内容:开发环境下,删除子组件(
comp
)在当前自定义元素中的样式:- 从
_styleChildren
中移除子组件,清理_childStyles
中对应的样式标签并从 DOM 移除。
- 从
- 作用:配合 HMR 或子组件卸载,清理子组件样式,避免样式污染。
参考资料
- 【源码阅读】@vue/shared —— Vue3 内部工具函数库
- 自定义元素最佳做法