使用 JavaScript Mixins 的优点
什么是 Mixin?
在开始使用某样东西之前,我们需要了解它是什么以及我们可以用它做什么。
什么是 Mixin?
mixin 是一个抽象子类;即,可以应用于不同超类以创建相关的修改类系列的子类定义。
- Gilad Bracha 和 William Cook,基于 Mixin 的继承
以日志记录为例。假设你有 3 个页面
- 红色的
- 绿色的
- 蓝色的
+----------+
| Page |
+----------+
| | |
+----------+ | +-----------+
| | |
+---------+ +-----------+ +----------+
| PageRed | | PageGreen | | PageBlue |
+----+----+ +-----------+ +----------+
class Page {}
class PageRed extends Page {}
class PageGreen extends Page {}
class PageBlue extends Page {}
现在,我们希望每当有人访问 Page Red 时都进行记录。
为了实现这一点,我们扩展了 Page Red,并创建了一个 Logged Page Red。
+----------+
| Page |
+-+--+--+--+
| | |
+----------+ | +-----------+
| | |
+----+----+ +-----+-----+ +-----+----+
| PageRed | | PageGreen | | PageBlue |
+----+----+ +-----------+ +----------+
|
+----+----+
| Logged |
| PageRed |
+---------+
class Page {}
class PageRed extends Page {}
class PageGreen extends Page {}
class PageBlue extends Page {}
class LoggedPagRed extends PageRed {}
如果我们想开始记录 PageGreen 的日志,我们会遇到一个问题:
- 我们不能把逻辑放进去,
Page
因为蓝色不应该被记录 - 我们无法重复使用其中的逻辑,
Logged PageGreen
因为我们无法从两个来源扩展(即使可以,也意味着红色和绿色中的信息相互冲突)
我们能做的就是把它放在一个“外部”的地方,然后写下来,这样它就可以“混入”了。
+----------+ +----------+
| Page | | Logging* |
+-+--+--+--+ +----------+
| | |
+----------+ | +-----------+
| | |
+-----+----+ +-----+-----+ +-----+----+
| PageRed | | PageGreen | | PageBlue |
| with | | with | +----------+
| Logging* | | Logging* |
+----------+ +-----------+
// defining the Mixin
export const LoggingMixin = superclass =>
class LoggingMixin extends superclass {
// logging logic
};
class Page {}
// applying a Mixin
class PageRed extends LoggingMixin(Page) {}
class PageGreen extends LoggingMixin(Page) {}
class PageBlue extends Page {}
通过这种方法,我们可以将逻辑提取到单独的代码片段中,以便在需要时使用。
有关更深入的技术解释,请阅读《Real Mixins with JavaScript Classes》。
为什么需要对 Mixins 进行重复数据删除?
现在我们希望所有日志记录都记录到红、绿、蓝三个页面。
很简单——因为我们现在可以在页面本身上应用 LoggingMixin 了。
+----------+ +----------+
| Page | | Logging* |
| with | +----------+
| Logging* |
+-+--+--+--+
| | |
+----------+ | +-----------+
| | |
+-----+----+ +-----+-----+ +-----+----+
| PageRed | | PageGreen | | PageBlue |
+----------+ | with | +----------+
| Logging* |
+-----------+
然而,Green 团队急于上线,所以他们已经把 Mixin 应用于LoggingMixin
他们的 Page 类了。当我们把它应用到基Page
类时,Mixin 就被应用了两次 😱
突然间,Green 页面会把每个日志打印两次——这与我们最初的想法完全不同。
我们需要做的是确保每个 Mixin 只附加一次,即使我们尝试多次应用它。
一般来说,mixin 越通用,被多次使用的可能性就越高。作为 mixin 的作者,你无法控制它的使用方式,也无法预测它的使用情况。因此,为了安全起见,我们始终建议创建去重 mixin。
npm i @open-wc/dedupe-mixin
import { dedupeMixin } from '@open-wc/dedupe-mixin';
export const MyMixin = dedupeMixin(
superclass =>
class MyMixin extends superclass {
// your mixin code goes here
},
);
您可以在演示中看到确切的这种情况。
通过将 dedupeMixin 应用于 mixin 函数,在导出它之前,我们可以确保我们的 mixin 类只会生效一次,即使它被混合到继承链中的多个基类中。
- no-dedupe因记录两次 Green 而“失败”
- with-dedupe也成功记录了一次 Green
您可以在github上查看两者的源代码。
嵌套示例
你可能认为上面的例子太简单了,可以通过调整何时进行修改来解决。
然而,在大多数实际场景中,情况要复杂得多 🙈
Mixins 可以扩展,仅仅因为你导入了一个类,并不意味着这个类已经预先应用了一些 Mixins。
考虑这个例子:
+----------+ +----------+ +----------+
| Page | | Logging* | | Feature |
| with | +----+-----+ | with |
| Logging* | | | Metrics* |
+-+--+--+--+ +----+-----+ +----+--+--+
| | | | Metrics* | | |
+----------+ | +-----------+ +----------+ | +------
| | | |
+-----+----+ +-----+-----+ +-----+----+ +------+-------+
| PageRed | | PageGreen | | PageBlue | | WaterFeature |
+----------+ +-----------+ | with | +--------------+
| Metrics* |
+----------+
- 页面通常只需要记录
- 然而,还有更先进的度量系统,可以扩展日志记录
- 指标是针对功能单独开发的
- 当我们现在想要在蓝色页面上获取相同的指标时,我们会得到重复的日志记录,而没有有意识地应用日志记录(我们会
class PageBlue extends MetricsMixin(Page) {}
) - 在这些情况下,只有重复数据删除才能发挥作用
何时不使用 Mixin
你现在可能会想“水坝真强大”,你说得对。我们现在应该用它来做所有事情吗?当然不行。
仅当您确实需要访问实例本身时才使用它。以下是一些不建议使用 Mixin 的坏例子。
我想做if (this.isIE11()) { // ... }
对于任何“纯”函数,最好将其保留在类/原型之外。例如最好这样写
import { isIE11 } from './helpers.js';
if (isIE11()) {}
我想要一个特别的this.addEventListener()
首先,覆盖内置函数可能非常危险。当你在类中需要实现一个非常具体的用例时,覆盖内置函数可能没问题。但是,如果在使用 Mixin 时突然发生这种情况,就会非常令人困惑。
最好将这些作为额外的函数来传递。这样人们就可以选择这些特殊功能,而不会“污染”他们的原型链。
import { specialAddEventListener } from './helpers.js';
specialAddEventListener(this, 'click', () => console.log('clicked'));
其次,所有属性/方法对于类/原型来说都是全局的。这意味着如果两个 mixin 使用相同的名称,就会发生冲突。因此,请确保为私有/受保护的方法使用特定的名称;如果使用通用名称,请确保在 mixin 名称/文档中能够清晰地识别。
何时使用 Mixin
如果您需要访问类实例本身。这意味着每个实例可能都有不同的设置。
一个有效的例子是,LocalizeMixin
它允许你设置myEl.locale = 'de-DE';
。在这种情况下,Mixin 需要提供并响应此属性。
import { dedupeMixin } from '@open-wc/dedupe-mixin';
export const LocalizeMixin = dedupeMixin(
superclass =>
class LocalizeMixin extends superclass {
// this assumes a Mixin for LitElement
static get properties() {
return {
locale: { type: String }
};
}
updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('locale')) {
// react to locale change
}
}
},
);
我们学到了什么
使用 Mixins,您可以将共享逻辑引入多个类。它是一个非常强大的工具,但伴随强大而来的是责任。请务必负责任地使用它,并删除 Mixins 中的重复代码。
笔记
使用AsciiFlow制作的 Ascii 图形 照片
由Vania Shows在Unsplash上拍摄