打字稿在保留类型的同时优雅地强制执行约束

时间:2020-09-29 20:56:19

标签: typescript

所以最初是为了强制执行约束,所以我只会应用这样的接口

// this is the constraint
interface Topic {
  [key: string]: (...args: any[]) => Promise<any>
}

// object that must pass the constraint
const topic: Topic = {

  // GOOD: topic methods must conform
  async actuate(a: boolean) {}
}

// BAD: type signature broken
topic.actuate(true)

但是问题是topic的类型被简化为最低公分母,这是约束Topic —相反,我希望topic保留其详细类型actuate

之类的方法的签名

所以我发现了这种cr脚的骇客方式,可以兼得两全:

interface Topic {
  [key: string]: (...args: any[]) => Promise<any>
}

type TopicConstraint<T extends Topic> = T

const topic = {

  // GOOD: topic methods must conform
  async actuate(a: boolean) {}
}

// BAD: weird ugly unused type variable constraint hack
// BAD: all topic constraint errors actually land here
type TopicCheck = TopicConstraint<typeof topic>

// GOOD: type signature preserved
topic.actuate(true)

是否有一些更合适的方法可以更优雅地完成此任务?我不知道如何用打字稿表达这种约束情况

但是我还是忍不住觉得有什么特别之处

type TopicConstraint<T extends Topic> = T

这可能是关键...这让我想做非法打字稿之类的事情

// invalid, but i need something in the same spirit
const topic = TopicConstraint<{
  async actuate(a: boolean) {}
}>

// invalid, but i need something in the same spirit
const topic: TopicConstraint<@self> = {
  async actuate(a: boolean) {}
}

我正在寻找一种我找不到的语法..是否有其他方法?谢谢!

2 个答案:

答案 0 :(得分:2)

处理此问题的方法根本不是注释变量,而是让编译器推断其类型。然后,在以后的某个时间,当您在期望Topic的某个地方使用该变量时,会得到所需的错误那里

const topic = {
    async actuate(a: boolean) { }
};
topic.actuate(true);

const badTopic = {
    oops(a: boolean) { }
}
badTopic.oops(true);

// later ...

function topicTaker(t: Topic) {
    // do something with a Topic
}

topicTaker(topic); // okay

topicTaker(badTopic); // error!
// ------> ~~~~~~~~
// Property 'oops' is incompatible with index signature.

这可行,在某些用例中,这确实足够了:您实际上并不关心变量的类型是否正确,直到尝试使用它为止。

但是如果此错误检查与对象声明相距太远而对您没有帮助怎么办?


好吧,如果您想尽早发现错误,可以执行我通常做的事情,并创建一个 helper函数

const asTopic = <T extends Topic>(topic: T) => topic;

此受约束的通用标识函数仅在运行时返回其参数。但是在编译时,将要求topic可分配给Topic,而实际上没有扩大topic。不必注释变量,只需将它们初始化为helper函数的返回值即可:

const topic = asTopic({
    async actuate(a: boolean) { }
});

topic.actuate(true); // okay

const badTopic = asTopic({
    oops(a: boolean) { } // error!
//  ~~~~
// void is not assignable to type Promise<any>
})

您可以通过两种方式查看asTopic():作为替换注释以避免扩展(即使用asTopic()代替: Topic),或早期警告,表明您的对象不是所需的类型(即asTopic()的行为类似于topicTaker(),您可以在创建站点立即调用它而不是推迟它)。

重新阅读这个问题:这个asTopic()辅助函数可能就是您在TopicConstraint上遇到的事情。

Playground link to code

答案 1 :(得分:1)

一种选择是使用一个无操作函数,该函数在参数中具有相关的泛型,以便将其约束在适当的位置:

playground

// this is the constraint
interface Topic {
  [key: string]: (...args: any[]) => Promise<any>
}
function ensureTopic<T extends Topic>(topic: T){
    return topic;
}

// object that must pass the constraint
const topic = ensureTopic({

  // GOOD: topic methods must conform
  async actuate(a: boolean) {}
})

相关:Use function interface to ensure parameters but infer more specific return type

相关问题