- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
导读
CSSDirective是 @microsoft/fast-element 样式系统中的核心扩展点,它允许开发者在css()模板标签中插入自定义指令,将任意 CSS 片段、ElementStyles实例甚至CSSStyleSheet动态注入到组件的 Shadow DOM 样式中,同时可通过createBehavior()为宿主元素附加运行时行为。本文基于 1.x API 文档(fast-element.cssdirective.md),结合仓库源码与测试用例,完整讲解该类的两个关键方法、cssDirective()装饰器注册机制、css.partial部分样式片段以及底层collectStyles的插值原理,读完即可动手编写自己的 CSS 指令。
CSSDirective 类总览
在 1.x 版本的 API 文档中,CSSDirective被定义为一个可直接继承的类,用于在css()模板中作为插值指令使用:
export declare class CSSDirective它暴露了仅有的两个核心方法,也是实现自定义指令时必须实现/重写的全部契约:
| 方法 | 作用 | 返回值 |
|---|---|---|
| createCSS() | 创建一个要插入到 CSS 文档中的 CSS 片段 | ComposableStyles |
| createBehavior() | 创建一个要绑定到宿主元素上的行为 | Behavior | undefined |
这两个方法的分工非常清晰:createCSS()解决"样式写什么"的问题,createBehavior()解决"元素需要什么运行时行为"的问题。前者在编译期被css()引擎调用,后者在元素实例化时由元素控制器调用。
返回类型 ComposableStyles
createCSS()的返回类型 ComposableStyles 在文档中定义如下:
export declare type ComposableStyles = string | ElementStyles | CSSStyleSheet;也就是说,一个指令可以返回三种形式的样式结果:纯 CSS 字符串、已编译的ElementStyles实例,或浏览器原生CSSStyleSheet(构造样式表)。这三种形态都会被插入最终的元素样式集合。
返回值中的 Behavior
createBehavior()返回 Behavior 接口对象,该接口包含两个方法:
bind(source, context):将该行为绑定到指定数据源;unbind(source):从数据源上解绑。
当一个 CSS 指令既产出样式又产出行为时,样式部分负责视觉,行为部分负责诸如订阅、计算、更新 DOM 等运行时逻辑,两者配合即可实现"样式 + 逻辑"打包复用的复合能力。
css() 模板标签与指令插值链路
CSSDirective的使命是配合css()使用。在 fast-element.css.md 中,css()的签名是:
export declare function css( strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[] ): ElementStyles;其中values参数明确允许ComposableStyles与CSSDirective两种类型的插值值,返回值是 ElementStyles。文档备注指出:css 助手支持字符串与 ElementStyle 实例的插值。
底层实现:collectStyles 如何消费指令
在当前仓库源码 packages/fast-element/src/styles/css.ts 中,collectStyles函数精确实现了文档描述的行为,其处理插值的核心逻辑为:
if (CSSDirective.getForInstance(value) !== void 0) { value = (value as CSSDirective).createCSS(); }也就是说,当css()引擎遇到一个已注册的指令实例时,会先调用其createCSS()拿到真实的样式产物,再按产物类型分流处理:
- 产物是
ElementStyles或CSSStyleSheet:将当前累积的 CSS 字符串作为独立片段压入样式数组,再压入该样式对象,从而保持构造样式表的独立性与复用性; - 产物是字符串:直接拼接到当前 CSS 字符串片段中。
最终,css()将收集到的片段数组封装为新的ElementStyles实例返回:
export const css: CSSTemplateTag = ( strings: TemplateStringsArray, ...values: CSSValue[] ): ElementStyles => { return new ElementStyles(collectStyles(strings, values)); };从源码结构可以推断:css()是一个一次性的"快照"编译过程,createCSS()只会在模板组合期间被调用一次,产物被静态固化到ElementStyles中;而需要响应式更新样式的场景则要交给createBehavior()返回的 Behavior 在运行时完成。
注册机制:cssDirective() 装饰器与类型注册表
要让CSSDirective.getForInstance(value)识别某个类的实例,必须先注册该类型。当前仓库源码 packages/fast-element/src/styles/css-directive.ts 提供了完整的注册体系:
export const CSSDirective = Object.freeze({ getForInstance: registry.getForInstance, getByType: registry.getByType, define<TType extends Constructable<CSSDirective>>(type): TType { registry.register({ type }); return type; }, });三个静态入口的职责分别是:
getForInstance(instance):按实例逆查其所属类型是否已注册,css()的collectStyles正是用它判断插值值是否为指令;getByType(type):按类型直接查询定义元数据;define(type):将一个类登记进内部类型注册表,注册成功即被认定为合法指令。
为了方便声明式注册,仓库还提供了对应的装饰器工厂:
export function cssDirective() { return function (type: Constructable<CSSDirective>) { CSSDirective.define(type); }; }使用方式非常简洁,以官方测试 packages/fast-element/src/styles/styles.pw.spec.ts 中的用例为例:
class Directive { createCSS() { return "red"; } } cssDirective()(Directive); const styles = css` host: { color: ${new Directive()}; } `; // 断言:styles.styles[0] 包含 "host:" 与 "color: red;"注意:cssDirective()是一个工厂函数,需要调用后返回装饰器再作用于类,这一点与常见的@cssDirective()装饰器用法在写法上有细微差别,实际等价。
从 class 到 interface:源码中的演进痕迹
1.x API 文档将CSSDirective描述为export declare class CSSDirective,而当前仓库源码中它已演化为"接口 + 冻结命名空间"的组合形态:
export interface CSSDirective { createCSS(): ComposableStyles; } export interface CSSDirectiveDefinition<TType> { readonly type: TType; }这种演进在语义上是等价的:createCSS()契约被完整保留(这正是文档中方法的实现基础),同时静态注册能力被拆分到命名空间对象上,接口化的设计让用户可以用对象字面量(duck typing)直接实现指令,而不必强制继承基类。写自定义指令时,只需保证实例拥有createCSS()方法并被cssDirective()注册即可。
自定义 CSS 指令的三种产物形态(测试验证)
测试 packages/fast-element/src/styles/styles.pw.spec.ts 对createCSS()的三种返回形态分别做了端到端验证,这是理解指令能力的极佳参考:
形态一:返回 CSS 字符串
最常用也最简单,指令产物直接拼进模板:
class Directive { createCSS() { return "red"; } } cssDirective()(Directive); const styles = css` host: { color: ${new Directive()}; } `;最终生成的样式片段包含color: red;,测试断言成立。
形态二:返回 ElementStyles
指令产物是另一个css()模板的编译结果,会被作为一个独立样式单元压入数组:
const _styles = css` :host { color: red; } `; class Directive { createCSS() { return _styles; } } cssDirective()(Directive); const styles = css` ${new Directive()} `; // 断言:styles.styles.includes(_styles) 为 true由于_styles是独立实例,插值后它会原样出现在最终ElementStyles的片段数组中,从而天然支持样式复用与共享。
形态三:返回 CSSStyleSheet
指令产物是浏览器原生构造样式表(仅在不支持 adopted stylesheets 的环境下跳过测试):
const _styles = new CSSStyleSheet(); class Directive { createCSS() { return _styles; } } cssDirective()(Directive); const styles = css` ${new Directive()} `; // 断言:styles.styles.includes(_styles) 为 true这种形态允许将基于 Web Platform 原生构造样式表体系(如CSSStyleSheet.replaceSync)构建的样式资产直接引入 fast-element 组件。
createBehavior():向宿主元素注入运行时行为
createBehavior()返回的Behavior | undefined在文档中描述为"绑定到宿主元素的行为"。在 1.x 的Behavior接口(fast-element.behavior.md)中,其职责是"向视图或元素的 bind/unbind 操作贡献行为"。
从当前仓库源码 packages/fast-element/src/styles/host.ts 的HostBehavior接口可以印证这一设计意图:行为对象可以通过addedCallback、removedCallback、connectedCallback、disconnectedCallback等回调,在宿主控制器附加、连接等生命周期节点执行逻辑,并可通过控制器实例上的addStyles/removeStyles动态增减样式(host.ts)。
因此,一个典型的复合型指令可以是:
class ThemeDirective { createCSS() { return `:host { color: ${this.color}; }`; } createBehavior() { return { bind(source, context) { // 订阅主题变化、更新元素等运行时逻辑 }, unbind(source) { // 清理订阅 }, }; } }样式在编译期静态注入,行为在运行时动态接管——这正是 CSSDirective 区别于普通字符串插值的价值所在。
css.partial:指令的轻量变体
除了直接使用css(),css()上还挂载了一个partial方法,用于创建"部分 CSS"指令。测试 styles.pw.spec.ts 展示了其行为:
class MyDirective { createCSS() { return "red"; } } cssDirective()(MyDirective); const partial = css.partial`color: ${new MyDirective()}`; // partial.createCSS() 返回 "color: red"源码 css.ts 中的CSSPartial类实现了这一点:它同样实现CSSDirective接口,内部将收集到的样式片段缓存为ComposableStyles,并在createCSS()时返回。当片段多于一个且包含ElementStyles等复杂对象时,会自动包装为新的ElementStyles,从而支持将一段"半成品 CSS"作为整体再插入其他css()模板。
css.partial与CSSDirective的关系是:partial 是引擎内置的指令实例,而自定义指令让你可以把任意的字符串、样式对象甚至逻辑包装成同样的插值单元,二者在collectStyles中走的是同一条注册识别路径。
完整实战:编写并应用一个主题色指令
综合以上所有知识点,一个最小可运行的完整示例:
import { css, cssDirective } from "@microsoft/fast-element"; class AccentColorDirective { constructor(private color: string) {} createCSS() { return `color: ${this.color};`; } createBehavior() { return undefined; } } cssDirective()(AccentColorDirective); const styles = css` :host { ${new AccentColorDirective("#ff6600")}; } `;应用该styles到组件(如通过组件装饰器的styles选项或ElementStyles注入)后,宿主元素将获得主题色。若要实现响应式颜色切换,则把逻辑移入createBehavior(),在行为绑定期间监听主题状态并调用hostController.addStyles()/removeStyles()动态更新样式。
小结
CSSDirective是 fast-element 样式体系中最灵活的可扩展接口:
- 通过 createCSS() 支持字符串、
ElementStyles、CSSStyleSheet三种样式产物插值(collectStyles 实现); - 通过 createBehavior() 向宿主元素注入运行时行为(HostBehavior 生命周期);
- 通过 cssDirective() 装饰器与类型注册表完成指令注册,并配合 css.partial 构建可复用的部分样式片段;
- 官方测试 styles.pw.spec.ts 覆盖了三种产物的完整插值链路,可作为实现自定义指令的行为基准。
当你需要"一段可复用的动态样式 + 配套运行时逻辑"时,CSSDirective就是 fast-element 给出的官方答案。
- 前端
- UI组件
【免费下载链接】fast
The adaptive interface system for modern web experiences.
相关推荐
GhostCoder如何让LLM编辑大型代码库:40+内置动作系统全解析
GhostCoder如何让LLM编辑大型代码库:40+内置动作系统全解析 GhostCoder 是一个专注于 让 LLM 编辑大型代码库 的开源 AI 编程工具
前端UI组件深入解析 FAST 的 fastTabPanel 组件注册器:配置、模板与自定义样式
深入解析 FAST 的 fastTabPanel 组件注册器:配置、模板与自定义样式 FAST 组件库( @microsoft/fast components
前端UI组件深入解析 fast-components 的 menuStyles 变量:Menu 组件样式模板的签名、注册机制与自定义实践
深入解析 fast components 的 menuStyles 变量:Menu 组件样式模板的签名、注册机制与自定义实践 导读 menuStyles 是 @
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考