| 特性 | 状态 |
|---|---|
| 支持相邻 text 节点 inline 布局 | 待发布 |
| Font-face 支持 woff 格式 | 待发布 |
| WeUI 扩展库 | 灰度中 |
| 开发者工具支持热重载 | 开发中 |
| 截图渲染缓存 | 开发中 |
| Windows/Mac 端支持 | 规划中 |
| 企业微信支持 | 规划中 |
| 支持 reorder-list-view 排序组件 | 规划中 |
| 支持超大图查看器 | 规划中 |
| input 或 text 的选区弹出菜单支持自定义 | 规划中 |
| inline/inline-block 布局完整支持 | 规划中 |
| Media Query 支持 width/height 等选项 | 规划中 |
| 支持 css clip-path | 规划中 |
| 预渲染/预加载页面机制 | 规划中 |
| live-player/video 小窗播放 | 规划中 |
作者: Next
glass-easel :新版微信小程序组件框架
glass-easel 是微信小程序组件框架的核心实现。它实质上是一个 JavaScript 的组件化界面框架,用来进行组件化、定义式的界面开发。
glass-easel 是对旧版微信小程序组件框架的重写,保持对旧版微信小程序组件框架特性的兼容,并添加了一些新特性。它运行时并不依赖于微信小程序环境,可以独立运行在 web 或其他 JavaScript 环境下。
主要特点
glass-easel 可以让同样的组件代码运行在 web 、微信小程序等不同环境下。
后端 是 glass-easel 的一个重要概念,表示组件系统的运行环境。在 web 环境下运行时,后端是浏览器的 DOM 接口;在微信小程序环境下运行时,后端则是微信小程序环境接口。这使得(后端无关的)组件代码可以运行在不同环境下。
glass-easel 完整具备微信小程序自定义组件相关特性,如组件模板、通信与事件、生命周期等等。此外, glass-easel 还实现了一些实用的新特性,也具有更好的 TypeScript 支持。
glass-easel 采用单组件节点树更新算法(大体上沿用了旧版微信小程序组件框架的更新算法),具有均衡的性能表现,适合高度组件化开发。
glass-easel 组件框架在 GitHub 上开源,代码和更详细的文档、示例等可以在 GitHub 上找到。
适配指引
glass-easel 适配指引 中列举了一些相较于现有组件框架 exparser 需要变更的逻辑,可以用于将现有的微信小程序迁移到新的框架,也可以用于快速了解新旧框架之间的差异。
glass-easel 适配指引
glass-easel 是一个新的组件框架,是对旧版组件框架 exparser 的一个重写,拥有 比旧版组件框架更好的性能和更多的特性。
将现有的运行在 exparser 上的微信小程序迁移到 glass-easel 需要少量的适配,下面的文档会为适配提供一些指引。
运行环境
Skyline 渲染引擎 目前全量运行在 glass-easel 上,因此页面 必须 适配 glass-easel 才能正常运行,在开发者工具中预览或上传时会有相应的提示和检查;
WebView 后端的 glass-easel 适配正在进行中,目前正在进行小范围的测试与灰度。
使用微信开发者工具进行调试时,glass-easel 需要 1.06.2308142 或更高版本的工具;当工具版本不支持使用 glass-easel 时,基础库将中断渲染并提示升级。
在运行过程中,vConsole 内的路由日志可以协助确认当前正在使用的组件框架:

JSON 配置
通过在页面或自定义组件的 JSON 配置中添加以下配置开始适配:
{ "componentFramework": "glass-easel" }
添加后,WXML 模板将被编译为适配 glass-easel 的新格式 ProcGen,并同时保持对旧版组件框架 exparser 兼容。
为一个页面或自定义组件添加这个配置后,所有它依赖的组件也将自动被标记为 glass-easel 适配(包括 usingComponents 依赖和 componentGenerics#default 依赖)
在 app.json 中添加这个配置可以全局开启 glass-easel 支持。但需要注意的是,配置后编译生成的模板虽然也能在 exparser 上运行,但兼容版本在 exparser 上有可能遇到边界情况下的兼容性问题,因此除非不需要兼容旧版本基础库或者微信小程序整体都以 Skyline 运行,否则应该更谨慎地使用全局配置。
插件暂未支持页面或自定义组件级别的 componentFramework 配置项,可以在 plugin.json 中添加这个配置项来开始适配。
变更点适配
glass-easel 在设计上兼容绝大多数的旧版组件框架 exparser 的接口,仅有少数地方需要变更:
- [必须] 模板中数据绑定外的转义改为标准 XML 转义,数据绑定内的转义现在无需转义
- 兼容性:[需要手动兼容] exparser 上不能使用新的转义写法
- 旧例:
<view prop-a="\"test\"" prop-b="{{ test === \"test\" }}" /> - 新例:
<view prop-a=""test"" prop-b="{{ test === "test" }}" />
- [必须] 模板中不再支持
wx-if,wx-for两种写法,仅支持wx:if,wx:for- 兼容性:[推荐直接变更] exparser 同样可以使用
wx:if,wx:for - 旧例:
<view wx-if="{{ arr }}" /> - 新例:
<view wx:if="{{ arr }}" />
- 兼容性:[推荐直接变更] exparser 同样可以使用
- [必须] 在
wx:for中使用<include>时,被引入的模板中的,不再有效,需要改为<template>- 兼容性:[推荐直接变更] exparser 同样可以使用
<template> - 旧例:
<block wx:for="{{ arr }}"> <include src="inc.wxml" /> </block> <!-- inc.wxml --> <view>{{ index }}. {{ item }}</view> - 新例:
<import src="inc.wxml" /> <block wx:for="{{ arr }}"> <template is="wx-for-content" data="{{ index, item }}" /> </block> <!-- inc.wxml --> <template name="wx-for-content"> <view>{{ index }}. {{ item }}</view> </template>
- 兼容性:[推荐直接变更] exparser 同样可以使用
- [可选] 由于兼容需要,
wx.createSelectorQuery性能不如this.createSelectorQuery,应尽量使用后者- 兼容性:[推荐直接变更] exparser 同样支持
this.createSelectorQuery - 旧例:
wx.createSelectorQuery() .in(this) .select('#webgl') .exec(res => { }) - 新例:
this.createSelectorQuery() .select('#webgl') .exec(res => { })
- 兼容性:[推荐直接变更] exparser 同样支持
- [必须]
SelectorQuery等接口中的选择器现在和 CSS 选择器一样,不再支持以数字开头- 兼容性:[推荐直接变更]
- 旧例:
this.createSelectorQuery() .select('#1') .exec(res => { }) - 新例:
this.createSelectorQuery() .select('#element-1') .exec(res => { })
- [必须] [仅 Skyline] Skyline 渲染后端上的 Worklet 回调函数名称变更
兼容性:[推荐直接变更] 旧版本基础库同样支持这些事件名称
变更对应:
组件名 原 Worklet 事件名 新 Worklet 事件名 pan-gesture-handler on-gesture-eventworklet:ongesturepan-gesture-handler should-response-on-moveworklet:should-response-on-movepan-gesture-handler should-accept-gestureworklet:should-accept-gesturescroll-view bind:scroll-startworklet:onscrollstartscroll-view bind:scrollworklet:onscrollupdatescroll-view bind:scroll-endworklet:onscrollendscroll-view adjust-deceleration-velocityworklet:adjust-deceleration-velocityswiper bind:transitionbind:animationfinishworklet:onscrollstartworklet:onscrollupdateworklet:onscrollendshare-element on-frameworklet:onframe旧例:
<scroll-view bindscroll="onScrollWorklet" /> <swiper bind:transition="onTransitionWorklet" />新例:
<scroll-view worklet:onscrollupdate="onScrollWorklet" /> <swiper worklet:onscrollstart="onTransitionWorklet" worklet:onscrollupdate="onTransitionWorklet" worklet:onscrollend="onTransitionWorklet" />
- [必须] [仅 Skyline] Skyline 渲染引擎 暂时不支持以下组件实例方法:
animateapplyAnimationclearAnimationsetInitialRenderingCache
已知问题
- 运行在
exparser兼容模式上时,text组件无法换行
更新记录
2023-06-01支持 WXS- 重新预览或上传即可,无版本依赖
2023-06-02修复 嵌套的wx:for可能导致异常 [wechat-miniprogram/glass-easel#45]- 重新预览或上传即可,无版本依赖
2023-06-02修复<template name>中使用的 WXS 在引用到其他文件中时可能失效 [wechat-miniprogram/glass-easel#47]- 重新预览或上传即可,无版本依赖
2023-06-12修复<template>,<include>,<slot>节点上不支持wx:指令 [wechat-miniprogram/glass-easel#30]- 重新预览或上传即可,无版本依赖
2023-07-28支持兼容模式下 WXS 事件响应中ComponentDescriptor的getState方法- 需要基础库版本 3.0.0 或以上,正在逐步支持到版本 2.19.2
2024-05-20支持全空的数据绑定- 重新预览或上传即可,无版本依赖
2024-10-18支持在组件 JS 的 options 中定义styleIsolation和addGlobalClass- 需要基础库版本 3.6.3 或以上,后续争取兼容到版本 3.0.0
2024-10-28支持 WXS 事件响应函数- 需要基础库版本 3.6.4 或以上,后续争取兼容到版本 3.0.0
2024-11-28Skyline 现全量运行在 glass-easel 上,因此不再需要进行 CSSinput标签选择器的兼容- 开发者无需任何操作
适用于 glass-easel 组件框架的特性
部分自定义组件特性仅适用于 glass-easel 组件框架 。
由于目前 glass-easel 组件框架仅可用于 Skyline 渲染引擎,因此这些特性也同样受此限制。
在模板中调用 data 里的函数
由于目前 glass-easel 组件框架仅可用于 Skyline 渲染引擎,因此这些特性也同样受此限制。
如果 data 中的某个字段是函数,在模板里可以直接调用它:
Component({
data: {
getDataField() {
return 'someValue'
},
},
})
<view>{{ getDataField() }}</view>
尽管这样做有时会很方便,但在实践中依然不建议滥用。
从代码可维护性的角度看, data 中的内容应当与数据内容强相关。如果函数的主要目的是对数据展示方面的预处理,推荐用 WXS 的方式,将函数实现内联在模板中。
链式调用API
由于目前 glass-easel 组件框架仅可用于 Skyline 渲染引擎,因此这些特性也同样受此限制。
Chaining API 接口形式
Chaining API 是一种新的页面和自定义组件定义形式。
对于一个传统的自定义组件定义:
Component({
properties: {
myProperty: String,
myAnotherProperty: String,
},
data: {
myDataField: 'someValue',
},
})
它可以被等价地写成以下 Chaining API 形式:
Component()
.property('myProperty', String)
.property('myAnotherProperty', String)
.data(() => ({
myDataField: 'someValue',
}))
.register()
使用 Chaining API 的主要好处是它具有更好的 TypeScript 支持,且对于复杂组件更加友好,还可以配合 init 函数 来使用。但它也使得对简单组件的定义看起来稍显繁琐。
因而,每个组件都可以分别选用传统的定义方式或者 Chaining API 来进行定义,可以对于每个组件都选用更合适它的定义方式。
常用的链式调用项
以下是一些常用链式调用项。
.property 用来定义单个属性,等价于传统形式的 properties 定义段中的单个项目。例如:
Component()
.property('myProperty', {
type: String
})
.register()
.data 用来定义数据字段表,作用上相当于传统形式的 data 定义段,但它接受一个函数。这个函数在每次组件创建时执行一次,它的返回值被用作数据字段。例如:
Component()
.data(() => ({
myDataField: 'someValue',
}))
.register()
.externalClasses 用来定义外部样式类,等价于传统形式的 externalClasses 定义段。例如:
Component()
.externalClasses(['my-class'])
.register()
.options 用来指定组件选项,等价于传统形式的 options 定义段。(注意,如果多次调用,仅有最后一次调用有效。)例如:
Component()
.options({
multipleSlots: true,
})
.register()
.options 用来指定组件选项,等价于传统形式的 options 定义段。(注意,如果多次调用,仅有最后一次调用有效。)例如:
Component()
.options({
multipleSlots: true,
})
.register()
以下链式调用项也是可用的,但通过 init 函数 来调用通常更加友好。
.methods 用来定义一组方法,等价于传统形式的 methods 定义段。例如:
Component()
.methods({
myMethod() { /* ... */ }
})
.register()
.lifetime 和 .pageLifetime 分别用来定义单个生命周期方法和组件所在页面的生命周期方法,等价于传统形式的 lifetime 和 pageLifetime 定义段中的单个项目。例如:
Component()
.lifetime('attached', function () { /* ... */ })
.pageLifetime('show', function () { /* ... */ })
.register()
.observer 用来定义单个数据监听器,类似于传统形式的 observers 定义段中的单个项目,但在同时监听多个数据字段时,应写成数组形式。例如:
Component()
.data(() => ({
a: 1,
b: 2,
}))
.observer(['a', 'b'], function () { /* ... */ })
.register()
.relation 用来定义单个组件间关系项,等价于传统形式的 relations 定义段中的单个项目。例如:
Component()
.relation('another-component', {
type: 'parent',
})
.register()
在链式调用项中使用 behavior
类似地, Behavior 也支持 Chaining API 。例如:
const beh = Behavior()
.property('myProperty', String)
.register()
这样,在组件中,可以使用 .behavior 将其引入:
Component()
.behavior(beh)
.register()
需要注意的是,引入 behavior 导致出现了重复的同名属性或同名数据字段时, TypeScript 将会报出类型错误。
重复使用链式调用项
除了 options 和 export ,其他链式调用项都可以重复调用多次,调用结果会组合起来。
这样可以把复杂的组件拆解成好几个部分来定义,对于很复杂的组件定义会有帮助。
Component()
// 定义 myDataField 字段和相关的处理逻辑
.data(() => ({
myDataField: 'someValue',
}))
.lifetime('attached', function () {
this.setData({ myDataField: updatedValue })
})
// 定义 anotherField 字段和相关的处理逻辑
.data(() => ({
anotherField: 1,
}))
.lifetime('attached', function () {
this.setData({ anotherField: updatedValue })
})
.register()
非连续链式调用
链式调用项也可以分开写。例如:
const componentDefinition = Component()
componentDefinition.property('myProperty', String)
componentDefinition.data(() => ({
myDataField: 'someValue',
}))
componentDefinition.register()
但这样写会丢失部分 TypeScript 类型信息。这种做法比较适合制作中间件、将 Component() 封装成别的形式的调用时。手工编写代码时并不建议这么做。
Chaining API 的 init 函数
由于目前 glass-easel 组件框架仅可用于 Skyline 渲染引擎,因此这些特性也同样受此限制。
init 链式调用项
在 Chaining API 中支持 .init(...) 链式调用项,可以以另一种方式进行组件创建:
Component()
.data(() => ({
myDataField: 'someValue',
}))
.init(function ({ lifetime }) {
// 这里可以用 JavaScript 局部量
const getUpdatedValue = () => {
return 'updated'
}
// 定义一个生命周期方法
lifetime('attached', () => {
this.setData({ myDataField: getUpdatedValue() })
})
})
.register()
init 中定义的函数会在每次组件创建时被调用一次。
这种方式的主要好处是在其内部可以自由使用 JavaScript 局部变量,减少对组件 this 的使用,有时会很方便。
init 函数中的辅助方法
init 的第一个参数包含多个辅助方法,可以用于组件定义。
method 用来定义单个方法,等价于传统形式的 methods 定义段中的单个项目。不过,它通常只用来定义事件响应函数,而且在末尾需要返回出来。例如:
Component()
.init(function ({ method }) {
const tapHandler = method(() => {
/* ... */
})
return { tapHandler }
})
.register()
lifetime 和 pageLifetime 分别用来定义单个生命周期方法和组件所在页面的生命周期方法,等价于传统形式的 lifetime 和 pageLifetime 定义段中的单个项目。例如:
Component()
.init(function ({ lifetime, pageLifetime }) {
lifetime('attached', () => { /* ... */ })
pageLifetime('show', () => { /* ... */ })
})
.register()
observer 用来定义单个数据监听器,类似于传统形式的 observers 定义段中的单个项目,但在同时监听多个数据字段时,应写成数组形式。例如:
Component()
.data(() => ({
a: 1,
b: 2,
}))
.init(function ({ observer }) {
observer(['a', 'b'], () => { /* ... */ })
})
.register()
relation 用来定义单个组件间关系项,等价于传统形式的 relations 定义段中的单个项目。例如:
Component()
.init(function ({ relation }) {
relation('another-component', {
type: 'parent',
})
})
.register()
需要注意的是,上面这些方法都不能异步或延迟执行,否则会报错:
Component()
.init(function ({ lifetime }) {
setTimeout(() => {
// 不能这么做!
lifetime('attached', () => { /* ... */ })
}, 0)
})
.register()
此外,第一个参数中还包含有 data 和 setData ,可以用来快速访问和设置数据。例如:
Component()
.data(() => ({
myDataField: 'someValue',
}))
.init(function ({ lifetime, data, setData }) {
lifetime('attached', () => {
setData({
myDataField: data.myDataField + ' updated',
})
})
})
.register()
但要注意 data 和 setData 只应在各个回调函数中使用,下面这样做会报错:
Component()
.init(function ({ setData }) {
setData({ /* ... */ })
})
.register()
动态 slot
由于目前 glass-easel 组件框架仅可用于 Skyline 渲染引擎,因此这些特性也同样受此限制。
静态 slot 与动态 slot
简单的自定义组件 slot 类型有两种:单一 slot 和多 slot ,取决于自定义组件的 multipleSlots 选项。它们都属于静态 slot 。
它们都要求(相同 name 的) slot 节点只有一个,重复的 <slot /> 中只有第一个会生效。
之所以称其为“静态”,是因为无论组件的实现如何, slot 的内容(由组件使用者提供)只会出现一次,不会因 <slot /> 的重复而重复。这样组件的使用者更容易控制它自身的节点。
从性能上看,单一 slot 也具有相对最优的性能表现。
但有时需要在列表中使用 slot 使得 slot 的内容被重复多次。此时可以使用动态 slot 。
Component({
options: {
dynamicSlots: true, // 启用动态 slot
},
data: {
list: ['A', 'B', 'C'],
},
})
然后,在模板中可以使 <slot /> 重复多次:
<block wx:for="{{ list }}">
<slot />
</block>
通过动态 slot 传递数据
在动态 slot 中,被重复的 <slot /> 可以分别携带不同的数据。例如:
<block wx:for="{{ list }}">
<slot list-index="{{ index }}" item="{{ item }}" />
</block>
上述的 slot 中携带有 list-index 和 item 两个数据项。
组件的使用者可以通过 slot: 来接收 slot 传递的任何数据项。例如:
<view>
<child>
<view slot:item>{{ item }}</view>
<view slot:listIndex>{{ listIndex }}</view>
</child>
</view>
组件的使用者在接收 slot 传递的数据项时,可以更改数据项的字段名。例如:
<view>
<child>
<view slot:listIndex="index">{{ index }}</view>
</child>
</view>
自定义组件
从微信小程序基础库版本 1.6.3 开始,微信小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
创建自定义组件
类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):
{
"component": true
}
同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。具体细节和注意事项参见 组件模板和样式 。
代码示例:
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
/* 这里的样式只应用于这个自定义组件 */
.inner {
color: red;
}
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。
在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。
组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。更多细节参见 Component构造器 。
代码示例:
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
使用自定义组件
使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。
开发者工具 1.02.1810190 及以上版本支持在 app.json 中声明 usingComponents 字段,在此处声明的自定义组件视为全局自定义组件,在微信小程序内的页面或自定义组件中可以直接使用而无需再声明。
代码示例:
在开发者工具中预览效果
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
自定义组件的 wxml 节点结构在与数据结合之后,将被插入到引用位置内。
注意事项
一些需要注意的细节:
- 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
- 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用
usingComponents字段)。 - 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
注意,是否在页面文件中使用 usingComponents 会使得页面的 this 对象的原型稍有差异,包括:
- 使用
usingComponents页面的原型与不使用时不一致,即Object.getPrototypeOf(this)结果不同。 - 使用
usingComponents时会多一些方法,如selectComponent。 - 出于性能考虑,使用
usingComponents时,setData内容不会被直接深复制,即this.setData({ field: obj })后this.data.field === obj。(深复制会在这个值被组件间传递时发生。)
如果页面比较复杂,新增或删除 usingComponents 定义段时建议重新测试一下。
组件系统
微信小程序的组件系统与常见的前端框架有所不同,微信小程序的运行时组织结构包含了一些独特的设计和概念。
Shadow 树与组件封装
微信小程序采用了类似 Shadow DOM 的概念来实现组件的封装。每个自定义组件都拥有自己的 Shadow 树,从而将组件的样式和结构与外部环境隔离。这种隔离机制使得组件更加模块化,可以独立管理自己的样式和逻辑,避免受到全局样式的干扰。
基本概念:
- Host Element: 即自定义组件本身,拥有一个 Shadow Root 节点。
- Shadow Root: Shadow 树的根节点,通常包含组件的 WXML 内容。
- Shadow Tree: 被 Shadow Root 包裹的子树,里面内容通常为 WXML 内写的内容。
- Slot Element: WXML 中写的
<slot>节点,用于插入 Host 元素的子节点到 Shadow 树中的指定位置。
代码示例:
<!-- 这是自定义组件 comp 的 WXML 结构 -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
<!-- 这是使用 comp 的 WXML 结构 -->
<comp>
<view class="outer"></view>
</comp>
最终所构造出的树结构为:
<comp> <!-- 这是 Host Element -->
#shadow-root <!-- 这里是 comp 组件的 Shadow 树 -->
<view class="inner">{{innerText}}</view>
<slot></slot> <!-- outer 会被插入到这里 -->
<view class="outer"></view> <!-- outer 不属于 comp 组件的 Shadow 树 -->
</comp>
Shadow 树的限制
通过 Shadow 树提供的封装能力,组件间的样式和逻辑得到了更明确的隔离,从而避免了外部的干扰。具体来说,Shadow 树有以下几个限制:
1. 样式隔离
组件的样式被与其他组件的 WXSS 以及全局的 app.wxss 隔离开来,彼此间无法互相覆盖或影响,也无法直接使用全局的 class。
提示: 你可以通过 组件样式隔离 或 外部样式类 等方式,灵活地控制样式的隔离。
2. 事件隔离
通过 triggerEvent 触发的事件会被限制在 Shadow 树内进行捕获和冒泡,外部组件无法直接监听这些事件。
提示: 你可以通过设置 composed 参数,改变事件的冒泡和捕获行为,从而允许外部监听。
3. SelectQuery 隔离
使用 SelectorQuery 获取节点时,只能获取当前 Shadow 树内的节点,无法访问其他组件的 Shadow 树或外部元素。
4. slot 状态
默认情况下,Host 节点的子节点的 attached 和 detached 状态仅与节点本身的挂载状态相关,而与 Shadow 树中的 <slot> 节点挂载状态无关。
<comp> <!-- 这是 Host Element -->
#shadow-root <!-- 这里是 comp 组件的 Shadow 树 -->
<view class="inner">{{innerText}}</view>
<slot wx:if="{{showSlot}}"></slot> <!-- outer 会被插入到这里 -->
<view class="outer"></view> <!-- 即使 showSlot 为 false,outer 也是 attached 状态 -->
</comp>
提示: 你可以设置 动态 slot 改变此行为。
Composed 树
在页面和组件渲染结构中,经过剔除虚拟节点和 Shadow Root 节点后,子节点会插入到对应的 <slot> 位置,最终形成 Composed 树。
它更加贴近实际的渲染结果,可以帮助开发者更清晰地看到组件和节点之间的真实关系。
提示: 你可以在调试面板中切换 Shadow 树和 Composed 树的展示
