混入

类混入(Class mixins)是使用标准 JavaScript 在类之间共享代码的模式。与响应式控制器这样的"has-a"(有一个)组合模式不同,在"has-a"模式中,一个类可以_拥有_一个控制器来添加行为,而混入实现的是"is-a"(是一个)组合,其中混入使类本身_成为_所共享行为的一个实例。

你可以使用混入通过添加 API 或覆盖其生命周期回调来自定义 Lit 组件。

混入可以被视为"子类工厂",它们覆盖它们所应用的类并返回一个子类,该子类扩展了混入中的行为。因为混入是使用标准 JavaScript 类表达式实现的,所以它们可以使用子类化可用的所有习语,比如添加新的字段/方法、覆盖现有的超类方法以及使用 super

为了便于阅读,本页面上的示例省略了混入函数的一些 TypeScript 类型。有关在 TypeScript 中正确类型化混入的详细信息,请参见 TypeScript 中的混入

要定义一个混入,编写一个接受 superClass 的函数,并返回一个扩展它的新类,根据需要添加字段和方法:

要应用混入,只需传入一个类来生成应用了混入的子类。最常见的是,用户在定义新类时直接将混入应用到基类:

混入也可以用来创建具体的子类,用户随后可以像普通类一样扩展这些子类,其中混入是一个实现细节:

因为类混入是一个标准的 JavaScript 模式而不是 Lit 特有的,所以社区中有大量关于利用混入进行代码重用的信息。要了解更多关于混入的信息,这里有一些不错的参考资料:

应用于 LitElement 的混入可以实现或覆盖任何标准的自定义元素生命周期回调,如 constructor()connectedCallback(),以及任何响应式更新生命周期回调,如 render()updated()

例如,以下混入会在元素被创建、连接和更新时记录日志:

注意,混入应该始终对 LitElement 实现的标准自定义元素生命周期方法进行 super 调用。当覆盖响应式更新生命周期回调时,如果超类上已经存在该方法,最好调用 super 方法(如上面使用可选链调用 super.updated?.() 所示)。

还要注意,混入可以选择在基本实现的标准生命周期回调之前或之后执行工作,这取决于它何时进行 super 调用。

混入还可以向子类元素添加响应式属性样式和 API。

下面示例中的混入向元素添加了一个 highlight 响应式属性和一个 renderHighlight() 方法,用户可以调用该方法来包装一些内容。当设置了 highlight 属性/特性时,包装的内容会被样式化为黄色。

注意在上面的示例中,混入的用户需要从他们的 render() 方法中调用 renderHighlight() 方法,并注意将混入定义的 static styles 添加到子类样式中。混入和用户之间的这种契约的性质取决于混入的定义,应该由混入作者进行文档说明。

在 TypeScript 中编写 LitElement 混入时,需要注意一些细节。

你应该将 superClass 参数限制为你期望用户扩展的类类型(如果有的话)。这可以通过使用通用的 Constructor 辅助类型来实现,如下所示:

上面的示例确保传递给混入的类扩展自 LitElement,这样你的混入就可以依赖 Lit 提供的回调和其他 API。

虽然 TypeScript 对使用混入模式生成的子类的返回类型有基本的推断支持,但它有一个严重的限制,即推断的类不能包含具有 privateprotected 访问修饰符的成员。

因为 LitElement 本身确实有私有和受保护的成员,默认情况下 TypeScript 在返回扩展 LitElement 的类时会报错:"Property '...' of exported class expression may not be private or protected."(导出的类表达式的属性 '...' 不能是私有或受保护的。)

有两种解决方法,都涉及转换混入函数的返回类型以避免上述错误。

当混入不添加新的公共/受保护 API

Permalink to "当混入不添加新的公共/受保护 API"

如果你的混入只是覆盖 LitElement 方法或属性,而不添加任何自己的新 API,你可以简单地将生成的类转换为传入的超类类型 T

当混入添加新的公共/受保护 API

Permalink to "当混入添加新的公共/受保护 API"

如果你的混入确实添加了你需要用户能够在其类上使用的新的受保护或公共 API,你需要单独定义混入的接口,并将返回类型转换为你的混入接口和超类类型的交集:

由于 TypeScript 类型系统的限制,装饰器(如 @property())必须应用于类声明语句而不是类表达式。

在实践中,这意味着 TypeScript 中的混入需要声明一个类然后返回它,而不是直接从箭头函数返回类表达式。

支持的写法:

不支持的写法: