【问题标题】:typescript elegantly enforce a constraint while preserving typestypescript 在保留类型的同时优雅地强制执行约束
【发布时间】:2020-09-29 20:56:19
【问题描述】:

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

// 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 保留其详细的类型签名,用于像 @987654325 这样的方法@

所以我发现这种蹩脚的 hacky 方法可以两全其美:

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)

有没有更合适的方法来更优雅地完成同样的事情?我不知道如何在 typescript 中表达这种约束情况

但我还是忍不住觉得有什么特别的地方

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) {}
}

我正在寻找一种我找不到的语法。有没有不同的方法?谢谢!

【问题讨论】:

    标签: typescript


    【解决方案1】:

    处理这个问题的方法是根本不注释你的变量,而是让编译器推断它的类型。然后,稍后,当您在需要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.
    

    所以这是可行的,在某些用例中这确实足够了:在尝试使用变量之前,您实际上并不关心变量是否属于正确的类型。

    但是如果这个错误检查离对象声明太远而对你没有帮助呢?


    好吧,如果你想早点发现错误,你可以按照我平时的做法,做一个辅助函数

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

    这个受约束的泛型标识函数只是在运行时返回它的参数。但在编译时,它将要求topic 可分配给Topic,而实际上没有扩大它到topic。无需注释变量,只需将它们初始化为辅助函数的返回值:

    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

    【讨论】:

    • 绝对棒极了!我已经采用了这个asTopic 模式,非常感谢!
    【解决方案2】:

    一种选择是使用在参数中具有相关泛型的无操作函数,以便对其进行约束:

    (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

    【讨论】:

      猜你喜欢
      • 2020-05-26
      • 2020-06-25
      • 2018-07-27
      • 2021-06-12
      • 2017-09-23
      • 1970-01-01
      • 2018-02-12
      • 2021-08-18
      • 1970-01-01
      相关资源
      最近更新 更多