返回介绍

模拟标称类型系统

发布于 2024-09-11 00:55:48 字数 1435 浏览 0 评论 0 收藏 0

类型的重要意义之一是限制了数据的可用操作与实际意义,它是通过类型附带的额外信息来实现的(类似于元数据)。

要在 TypeScript 中实现,其实也只需要为类型额外附加元数据即可,比如 CNY 与 USD,分别附加上它们的单位信息即可,但同时又需要保留原本的信息(即原本的 number 类型)。

declare class TagProtector<T extends string> {
  protected __tag__: T;
}

type Nominal<T, U extends string> = T & TagProtector<U>;

type CNY = Nominal<number, 'CNY'>;

type USD = Nominal<number, 'USD'>;

const CNYCount = 100 as CNY;

const USDCount = 100 as USD;

function addCNY(source: CNY, input: CNY) {
  return (source + input) as CNY;
}

addCNY(CNYCount, CNYCount);

// 报错了!
addCNY(CNYCount, USDCount);

使用 TagProtector 声明了一个具有 protected 属性的类,使用它来携带额外的信息,并和原本的类型合并到一起,就得到了 Nominal 工具类型。

这一实现方式本质上只在类型层面做了数据的处理,在运行时无法进行进一步的限制。可以从逻辑层面入手进一步确保安全性:

class CNY {
  private __tag!: void;
  constructor(public value: number) {}
}
class USD {
  private __tag!: void;
  constructor(public value: number) {}
}

使用方式也要进行变化:

const CNYCount = new CNY(100);
const USDCount = new USD(100);

function addCNY(source: CNY, input: CNY) {
  return (source.value + input.value);
}

addCNY(CNYCount, CNYCount);
// 报错了!
addCNY(CNYCount, USDCount);

通过这种方式,在运行时添加更多的检查逻辑,同时在类型层面也得到了保障。

这两种方式的本质都是通过额外属性实现了类型信息的附加,从而使得结构化类型系统将结构一致的两个类型也判断为不可兼容。

将其标记为 private / protected 其实不是必须的,只是为了避免类型信息被错误消费。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文