typescript 是否可以在编译时确保函数参数值的唯一性?

kq0g1dla  于 2023-03-24  发布在  TypeScript
关注(0)|答案(1)|浏览(84)

我有一个标识符的静态列表。我想使用其中一个标识符“标记”一个函数调用。我知道我可以做到以下几点:

type Id = 'id-1' | 'id-2';

function foo(id : Id) {
  console.log(`my id: ${id}`);
}

foo('id-1');
foo('id-2');

但我真正希望发生的是,如果将相同的id传递给函数,则会看到类型错误:

type MagicId = /* some magic type to achieve uniqueness */;

function foo(id : MagicId) {
  console.log(`my id: ${id}`);
}

foo('id-1');
foo('id-1'); // type error is thrown
enxuqcxy

enxuqcxy1#

有一些东西可以实现这一点,但你必须依靠对象:

type Id = 'id-1' | 'id-2';

function _foo(id: Id) {
  console.log(`My id: ${id}`)
}

function foo<T extends Id>(id : T) {
  _foo(id);
  return {
    foo(id: Exclude<Id, T>) {
      _foo(id);
    }
  }
}

foo('id-1').foo('id-2'); // ok

foo('id-1').foo('id-1'); // error

正如你所看到的,外部的foo可以接受"id-1""id-2",但是内部的不能,因为我们Exclude外部foo的类型。
如果你有连续的调用,这种方法就可以工作,否则你必须保存外部foo的结果,并假设你永远不会再次调用外部foo
这里是一个操场。
改进后的版本如下:

type Builder<T> = {
  foo<I extends T>(id: I): Builder<Exclude<T, I>>;
};

const builder = {
  foo<T extends Id>(id: T): Builder<Exclude<Id, T>> {
    console.log(`My id ${id}`);
    return this;
  },
};

builder.foo("id-1").foo("id-2");

这是递归的,并去掉了我们已经使用的类型:

builder
  .foo("id-1") // ok, we can still have "id-2"
  .foo("id-2") // ok, we have finished the values, so the next one will be `never`
  .foo("id-1") // error, "id-1" can't be assigned to `never`

这里是运动场

相关问题