如何区分多个模板文字字符串类型?如果我定义

type UnionTypeExample =  `${string}_id` | `${string}_username`

使用

x:  UnionTypeExample

how can I check if x is an id or a username? The temptation is to use some combination of includes() and "string", but this does not work 使用 type guarding.

现在我收到这样的错误

Argument of type '`https://${string}` | `${string}_id`' is not assignable to parameter of type '`https://${string}`'.
  Type '`${string}_id`' is not assignable to type '`https://${string}`'.(2345)

下面是代码示例,但这typescript playground example更清楚地显示了错误.

type Url = `https://${string}`;
type ID = `${string}_id`;
type AttributeType = number | Date | Url | ID;
type AttributeLabelProps = {
    attribute: number | Date | Url;
}

const handleNumber = (val: Number) => console.log(`${val}: is a number!`)
const handleDate = (val: Date) => console.log(`${val}: is a date!`)
const handleUrl = (val: Url) => console.log(`${val}: is a url!`)
const handleID = (val: ID) => console.log(`${val}: is an id!`)

function AttributeLabel(attribute: AttributeType) {
    if (typeof attribute === "number") {
        handleNumber(attribute)
    } 
    else if (attribute instanceof Date) {
        handleDate(attribute)
    }
    // What is the correct runtime check to narrow this type from Url | ID to just Url? 
    else if (typeof attribute === "string" && attribute.startsWith("https://")) {
        handleUrl(attribute) // error!
        //        ~~~~~~~~~
        // Argument of type '`https://${string}` | `${string}_id`' is 
        // not assignable to parameter of type '`https://${string}`'.
    } 
    else { 
        handleID(attribute)
        //       ~~~~~~~~~
        // Argument of type '`https://${string}` | `${string}_id`' is 
        // not assignable to parameter of type '`${string}_id`'.

    }
};


//This is what I would expect the output to be:
console.log(AttributeLabel(1)) // 1 is a number!
console.log(AttributeLabel(new Date())) // [TODAY] is a date!
console.log(AttributeLabel("https://google.com")) // https://google.com is a url!
console.log(AttributeLabel("dog_id")) // dog_id is an id! 

推荐答案

TypeScrip不会将呼叫the startsWith() string method解释为将其转换为适当的模式template literal type.call signature for startsWith() in the TypeScript library只返回boolean,而不是type predicate.此调用签名早于TypeScrip中模板文字类型的存在,并且在microsoft/TypeScript#46958中将其更改为类型保护的后续建议被拒绝.因此,它不是语言的一部分,您需要解决它.


如果您希望看到startsWith()在代码库中充当类型保护,您可以通过使用适当的调用签名merging in来实现:

interface String {
    startsWith<T extends string>(t: T): this is `${T}${string}`;
}

然后,您的代码将不需要重构即可正常工作:

function AttributeLabelPlain(attribute: AttributeType) {
    if (typeof attribute === "number") { handleNumber(attribute) }
    else if (attribute instanceof Date) { handleDate(attribute) }
    else if (typeof attribute === "string" && attribute.startsWith("https://")) {
        handleUrl(attribute) // okay
    }
    else { handleID(attribute) } // okay
};

当然,您可能不想这样做.全局执行的建议被拒绝了,因为这会使startsWith()的行为对编译器来说更加复杂,而且如果正在考虑的字符串文字类型是unions,则可能会导致性能问题.


另一种更自包含的方法是将判断重构为它自己的自定义类型保护函数,这样只有对该函数的调用才能充当类型保护,而不是每次调用startsWith().就像这样:

function isUrl(x: any): x is Url {
    return typeof x === "string" && x.startsWith("https://");
}

function AttributeLabel(attribute: AttributeType) {
    if (typeof attribute === "number") { handleNumber(attribute) }
    else if (attribute instanceof Date) { handleDate(attribute) }
    else if (isUrl(attribute)) { handleUrl(attribute) } // okay
    else { handleID(attribute) } // okay
};

我可能会 Select 后者,尽管您需要注意编译器只有believes,即您的isUrl()是正确实现的.如果您更改了Url的定义,则需要记住相应地更改实现;不会发出错误消息.

Playground link to code

Typescript相关问答推荐

类型脚本不会推断键的值类型

使用前的组件渲染状态更新

了解TS类型参数列表中的分配

node—redis:如何在redis timeSeries上查询argmin?

泛型类型,引用其具有不同类型的属性之一

为什么我的Set-Cookie在我执行下一次请求后被擦除

等待用户在Angular 函数中输入

如何从扩展接口推断泛型?

类型TTextKey不能用于索引类型 ;TOption

具有自引用属性的接口

跟踪深度路径时按条件提取嵌套类型

类型脚本-在调用期间解析函数参数类型

在单独的组件中定义React Router路由

当传递带有或不带有参数的函数作为组件props 时,我应该如何指定类型?

基于泛型的类型脚本修改类型

基于属性值的条件类型

我应该使用服务方法(依赖于可观察的)在组件中进行计算吗?

剧作家测试未加载本地网址

React router - 如何处理嵌套路由?

错误:仅允许在‘使用服务器’文件中导出异步函数