使用 JavaScript Mixins 优点 Mixin 是什么?

2025-06-10

使用 JavaScript Mixins 的优点

什么是 Mixin?

在开始使用某样东西之前,我们需要了解它是什么以及我们可以用它做什么。

什么是 Mixin?

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 类只会生效一次,即使它被混合到继承链中的多个基类中。

您可以在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 ShowsUnsplash上拍摄

鏂囩珷鏉ユ簮锛�https://dev.to/open-wc/using-javascript-mixins-the-good-parts-4l60
PREV
你需要放松一点,开发者
NEXT
使用 Twitter 机器人获取 Hey 邀请码