☰
FAST Element 的 slotted() 指令:观测 `<slot>` 分配节点并同步到属性的权威指南
2026/9/29 10:23:54 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】fast

The adaptive interface system for modern web experiences.

项目地址:https://gitcode.com/gh_mirrors/fa/fast
点击查看免费下载

slotted()是@microsoft/fast-element提供的模板指令(directive),用于观测某个<slot>元素通过assignedNodes()返回的分配节点,并在这些节点发生变化时自动更新组件上的指定属性。本文以仓库 API 文档 为骨架,结合 slotted.ts 源码、node-observation.ts 基类 与 Playwright 测试,完整讲解其函数签名、两种调用方式、可配置选项,以及如何用它在实际组件中构建可响应变化的插槽内容。

一、slotted() 是什么

在 Web Components 的 Shadow DOM 机制中,<slot>是宿主元素与影子树之间的"插槽",外部传入的 Light DOM 内容会被分配到插槽中。当我们需要在组件内部对这些被分配进来的节点(而不是组件自己渲染的节点)做引用、统计或渲染时,slotted()指令就是官方提供的桥梁。

按 fast-element.slotted.md 的官方定义:

A directive that observes theassignedNodes()of a slot and updates a property whenever they change.

即:观测某个 slot 的assignedNodes()返回值,并在其发生变化时把结果更新到一个属性上。

它属于"引用型指令"(referential directives)家族,与ref(引用单个 DOM 节点)、children(引用某个元素的所有子节点)并列。三者区别如下:

指令观测目标底层机制典型场景
ref单个模板内节点视图绑定直接拿到video、canvas等节点的引用
children某元素的所有子节点MutationObserver统计/遍历某容器下的全部子节点
slotted某个<slot>的分配节点slot 上的slotchange事件 +assignedNodes()感知外部传入插槽的内容变化

与children使用MutationObserver不同,slotted只监听slotchange事件,成本更低,且天然与 Shadow DOM 的插槽分配语义对齐。

二、函数签名与类型定义

slotted的官方签名(见 fast-element.slotted.md):

export declare function slotted<T = any>( propertyOrOptions: (keyof T & string) | SlottedBehaviorOptions<keyof T & string> ): CaptureType<T>;

当前仓库 1.x 系列中,实际导出实现(见 packages/fast-element/src/templating/slotted.ts):

export function slotted<TSource = any, TParent = any>( propertyOrOptions: | (keyof TSource & string) | SlottedDirectiveOptions<keyof TSource & string>, ): CaptureType<TSource, TParent> { if (isString(propertyOrOptions)) { propertyOrOptions = { property: propertyOrOptions }; } return new SlottedDirective( propertyOrOptions as SlottedDirectiveOptions<keyof TSource & string>, ); }

对签名做逐项拆解:

  • 参数propertyOrOptions:二选一。
    • 传一个字符串属性名(keyof T & string保证该名字必须是源类型T上真实存在的键),函数会自动把它包装为{ property: propertyOrOptions };
    • 传一个选项对象(SlottedBehaviorOptions),可同时配置property、filter以及assignedNodes()的参数;
  • 返回值CaptureType<TSource, TParent>:一个可供html模板编译器识别的捕获类型标记,表明该指令捕获了模板中的某个节点位置。TSource是视图数据源类型,TParent是父视图类型;
  • 泛型<TSource = any, TParent = any>:仓库源码使用TSource/TParent命名,而文档中的 API 摘要使用<T = any>,两者是同一泛型参数在不同生成阶段的不同命名,语义一致。

SlottedBehaviorOptions 选项接口

选项接口定义(fast-element.slottedbehavioroptions.md):

export interface SlottedBehaviorOptions<T = any> extends NodeBehaviorOptions<T>, AssignedNodesOptions

它由两部分组成:

  1. NodeBehaviorOptions<T>(定义于 node-observation.ts):
    • property: T:要把观测到的节点数组赋值到的属性名;
    • filter?: ElementsFilter:可选的节点过滤函数,签名(value: Node, index?: number, array?: Node[]) => boolean,对数组中的每个节点调用一次,返回true的节点才会被同步到属性。
  2. AssignedNodesOptions:即浏览器原生HTMLSlotElement.assignedNodes()的参数类型:
    • flatten?: boolean:若为true,返回的节点列表包含所有后备内容(fallback content)被扁平化后的节点;
    • 这些参数最终原样透传给 getNodes() 中的assignedNodes(this.options)。

便捷过滤器elements()

仓库在 node-observation.ts 中提供了一个开箱即用的过滤器:

const selectElements = (value: Node): boolean => value.nodeType === 1; export const elements = (selector?: string): ElementsFilter => selector ? value => value.nodeType === 1 && (value as HTMLElement).matches(selector) : selectElements;
  • 不带参数调用elements():只保留nodeType === 1的元素节点,过滤掉文本节点、注释节点等;
  • 带选择器调用elements('li'):在元素节点的基础上再用matches()做 CSS 选择器匹配;
  • 该工厂函数同时服务于children与slotted两个指令,属于共享工具。

三、核心用法:两种调用方式

方式一:直接传属性名

当组件上已经声明了一个属性(配合@observable可获得响应式更新),可以直接把属性名传给slotted():

import { FASTElement, customElement, html, slotted } from '@microsoft/fast-element'; const template = html<MyElement>` <div> <slot ${slotted('slottedNodes')}></slot> </div> `; @customElement({ name: 'my-element', template }) export class MyElement extends FASTElement { @observable slottedNodes: Node[]; slottedNodesChanged() { // 响应插槽节点变化 } }

这段示例取自官方 using-directives.md 文档。关键行为:

  • slottedNodes会被填充为该 slot 的assignedNodes()结果(Node[]);
  • 属性用@observable装饰后,插槽内容变化时属性会动态更新,模板中凡是依赖该属性的绑定都会自动重渲染;
  • 可选的*Changed变更回调(如slottedNodesChanged())会在节点变化时被调用;
  • 与ref、children类似,节点在connectedCallback生命周期之后才可用,官方文档专门给出提示:应优先依赖变更处理器,而不是假设节点在connectedCallback中已就绪。

方式二:传选项对象

当需要过滤节点或指定assignedNodes()参数时,改用对象形式:

import { FASTElement, customElement, html, slotted, elements } from '@microsoft/fast-element'; const template = html<MyElement>` <div> <slot ${slotted({ property: 'items', filter: elements('my-item') })}></slot> </div> `;

这里filter: elements('my-item')表示只有匹配my-item选择器的元素节点才会被同步到items属性,文本节点和其他元素都会被过滤掉。

四、运行时行为:源码级实现原理

slotted()返回的SlottedDirective(slotted.ts)继承了NodeObservationDirective基类,四个核心方法构成了完整的观测生命周期:

export class SlottedDirective extends NodeObservationDirective<SlottedDirectiveOptions> { observe(target: EventSource): void { target.addEventListener(slotEvent, this); // slotEvent = "slotchange" } disconnect(target: EventSource): void { target.removeEventListener(slotEvent, this); } getNodes(target: HTMLSlotElement): Node[] { return target.assignedNodes(this.options); } handleEvent(event: Event): void { const target = event.currentTarget as any; this.updateTarget(this.getSource(target), this.computeNodes(target)); } }

1. 监听机制:slotchange 事件

observe()在目标<slot>元素上注册slotchange事件监听器,disconnect()负责移除。当插槽的分配节点集合变化(如外部新增/移除子节点、节点被重分配)时,浏览器触发slotchange,指令通过handleEvent触发一次更新。

2. 取值机制:assignedNodes()

getNodes()调用原生assignedNodes(this.options)获取分配节点。传入的flatten等选项会在这里生效。从源码看,slotted不使用MutationObserver,因此只关心"分配到插槽的节点"变化,而非元素子树的任意变动——这是它与children在语义上的根本区别。

3. 过滤与赋值:computeNodes / updateTarget

基类 node-observation.ts 统一处理过滤与赋值:

protected updateTarget(source: any, value: ReadonlyArray<any>): void { source[this.options.property] = value; } protected computeNodes(target: any): Node[] { let nodes = this.getNodes(target); if ("filter" in this.options) { nodes = nodes.filter(this.options.filter!); } return nodes; }

即:先取原始节点 → 若有filter则过滤 → 一次性赋值给source[property]。因为属性是@observable的,赋值动作本身就会触发 FAST 的观察者通知机制,驱动依赖该属性的绑定与变更回调。

4. 绑定与解绑生命周期

基类的bind/unbind(node-observation.ts)完成视图绑定时的初始化与清理:

  • bind:通过controller.targets[this.targetNodeId]定位到模板中的<slot>节点,将 controller 挂到节点的内部属性上,立即用当前节点更新一次属性,然后调用observe()注册监听,并登记到controller.onUnbind(this);
  • unbind:先把属性置为空数组emptyArray,再disconnect()移除监听,最后清空挂在节点上的 controller 引用——确保视图销毁后不会再有回调泄漏或误更新。

5. 防御性细节

注意SlottedDirective.handleEvent与测试中都体现了两个工程细节:

  • 指令把自身作为事件处理器(this传给addEventListener),并通过event.currentTarget取目标节点,避免了额外的闭包分配;
  • 测试中专门验证了"DOM 被 JSON.stringify 时不应抛错"(slotted.pw.spec.ts 第 402-441 行),说明指令在节点上挂载的内部 controller 属性被设计为不可序列化侵入的(基类通过id生成的内部属性名_controllerProperty与noop化等机制规避了序列化问题)。

五、测试用例印证:六种行为契约

仓库的 slotted.pw.spec.ts 用 Playwright 在真实浏览器中验证了指令的行为契约,可作为理解slotted()语义的权威清单:

测试验证内容
"returns an SlottedDirective"slotted("test")返回的确实是SlottedDirective实例
"creates a behavior by returning itself"指令的createBehavior()返回自身(无状态无副作用的行为单例)
"gathers nodes from a slot"bind 后,属性被填充为 slot 的全部分配节点,顺序一致
"gathers nodes from a slot with a filter"传入filter: elements("foo-bar")后,属性只包含匹配元素
"updates when slotted nodes change"向宿主追加子节点并等待Updates.next()后,属性自动同步新增节点
"updates when slotted nodes change with a filter"节点变化后,过滤逻辑依然生效
"clears and unwatches when unbound"unbind 后属性被清空,且后续 DOM 变化不再更新属性
"should not throw if DOM stringified"对引用做JSON.stringify不抛异常

这组测试从"创建 → 绑定取值 → 过滤 → 动态更新 → 解绑清理 → 序列化安全"六个维度锁定了slotted()的完整行为,读者若在集成时遇到异常,可对照这些断言排查。

六、应用场景与对比选型

典型场景:感知外部传入的插槽内容

slotted()最常见的用途是让组件感知使用者传入的插槽内容。官方文档明确建议:优先使用变更处理器(*Changed)来响应插槽节点变化,而不是假设节点在connectedCallback时已经就绪。例如在fast-foundation系列组件中,select、listbox、toolbar、breadcrumb等组件都大量依赖slotted观测机制来收集插槽中的选项节点并做出响应(相关 API 可参见 fast-foundation 的 slotted 相关 API 文档)。

与children的选型对比

维度childrenslotted
观测对象元素的所有子节点(含未插槽的 Light DOM 节点)分配到指定<slot>的节点
底层机制MutationObserver(可选subtree)slotchange事件 +assignedNodes()
选项差异支持subtree(子树观测时必须提供selector)支持flatten(扁平化后备内容)
适用场景需要监控容器子树整体变化只关心进入某个插槽的分配结果

从 using-directives.md 的提示可知:若把children放在模板根<template>元素上,拿到的是自定义元素所有 Light DOM 子节点,无论它们是否被插槽分配;而slotted只反映分配到特定<slot>的节点。

七、小结

slotted()是 FAST Element 中连接"外部 Light DOM 内容"与"组件内部逻辑"的关键指令:

  1. 两种传参:字符串属性名(自动包装)或SlottedBehaviorOptions对象(可配property、filter、flatten);
  2. 响应式联动:配合@observable属性与*Changed回调,插槽节点变化时属性自动更新;
  3. 源码机制清晰:slotchange监听 +assignedNodes()取值 + 可插拔filter,由 SlottedDirective 与 NodeObservationDirective 共同实现;
  4. 行为有测试背书:slotted.pw.spec.ts 覆盖了从绑定、过滤、动态更新到解绑清理的完整契约。

当你需要组件对"外部传入了什么内容、什么时候变化"做出响应时,slotted()就是首选方案——在动手前,请先确认组件的属性已用@observable声明,并优先通过变更处理器响应节点变化。

  • 前端
  • UI组件

【免费下载链接】fast

The adaptive interface system for modern web experiences.

项目地址:https://gitcode.com/gh_mirrors/fa/fast
点击查看免费下载
上一篇:Assetic 项目常见问题解决方案
下一篇:DeepSeek Coder 33B Base未来发展趋势与技术路线图

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询