确定类型是“字符串”字面量、“数字”字面量还是“字符串 | 数字”字面量。

4
自从Typescript支持条件类型以来,我决定进行一些元编程来增加VSCODE智能感知的功能。然而,虽然可以使用A extends B轻松区分其他类型,但我很难确定提供的类型是否为字面量。
因此问题是 - 如何确定给定类型是否为字面量类型?

你是否认为"a" | "b"是字面类型?那么2 | string呢? - jcalz
我认为"a" | "b", 1 | 2,以及可能的"a" | 1都被视为字面类型。对我来说,任何其他的联合类型都是无效的。 - FrogTheFrog
2个回答

4

我不确定你的使用情况是什么。个人而言,我会这样做:

type IfStringOrNumberLiteral<T, Y=true, N=false> =
  string extends T ? N : // must be narrower than string
  number extends T ? N : // must be narrower than number
  [T] extends [never] ? N : // must be wider than never
  [T] extends [string | number] ? Y : // must be narrower than string | number
  N

我通常使用 --strictNullChecks,因此在处理 nullundefined 时可能会有所不同。当然,它可以根据您的特定需求进行修改。大多数情况下,我只是想展示一种替代形式,以避免使用类似于 ( X extends Y ? true : false ) extends true ? U : V 的迂回结构。

希望这有所帮助,祝好运。


这看起来非常干净!谢谢!只是好奇,[...] 只是为了美观而已吗? - FrogTheFrog
为了防止将条件类型分发到T上。由于T是所谓的“裸类型参数”,短语T extends string | number?Y:NT是像4 | object这样的联合类型时可能会变成Y | N,而我认为在这种情况下你只需要N。防止条件分发类型的方法是以某种(协变)方式“着装”裸类型参数。最简洁的方法是使用单元素元组括号语法。 - jcalz

0

编辑: 我已经重写了所有内容,以匹配jcalz的简洁风格:

type IsStringLiteral<T> =
    string extends T ? false : // must be narrower than string
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [string] ? true : // must be wider than string
    false;

type IsNumberLiteral<T> =
    number extends T ? false : // must be narrower than number
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [number] ? true : // must be wider than number
    false;

type IsSingleTypeLiteral<T> =
    IsStringLiteral<T> extends false ?
    IsNumberLiteral<T> :
    true;

type IsLiteral<T> =
    string extends T ? false : // must be narrower than string
    number extends T ? false : // must be narrower than number
    [T] extends [never | undefined | null] ? false : // must be wider than never and nullable
    [T] extends [number | string] ? true : // must be wider than number | string
    false;

这比预期的要难一些,但经过几个小时的努力,我终于实现了这个:

type Switch<A, B, IF, ELSE = A> = A extends B ? IF : ELSE;
type IsStringLiteral<T> =
    // Check for nullable type using Switch type. See next comment why Switch must be used.
    Switch<T, undefined | null, true, false> extends true ? false : (
        // `T extends string` does not work for `"str" | number` and etc. Results in `boolean` type.
        // Need to use boolean Switch to filter out false-positive.
        Switch<T, string, true, false> extends true ? (
            // `string` does not extend literal type.
            string extends T ? false : true
        ) : false
    );
type IsNumberLiteral<T> =
    Switch<T, undefined | null, true, false> extends true ? false : (
        Switch<T, number, true, false> extends true ? (
            number extends T ? false : true
        ) : false
    );
type IsSingleTypeLiteral<T> =
    Switch<IsStringLiteral<T>, false, IsNumberLiteral<T>, true>;
type IsLiteral<T> =
    // `"string literal" | string` and etc. will return a false-positive `boolean` type.
    // `boolean` type must always be `false`, thus `false extends boolean` is used to get that `false` type.
    Switch<false, Switch<T, undefined | null, true, false> extends true ? false : (
        T extends string | number ? (
            string extends T ? false : (number extends T ? false : true)
        ) : false
    ), false, true>;

下面是一些以 HTML 表格形式展示的测试用例(已使用 3.1.1 进行了测试):

table, th, td {
  white-space: nowrap;
  border: 1px solid black;
}
<table><tbody><tr><th>Test cases</th><th>IsStringLiteral</th><th>IsNumberLiteral</th><th>IsSingleTypeLiteral</th><th>IsLiteral</th></tr><tr><td>"string literal"</td><td><b>true</b></td><td>false</td><td><b>true</b></td><td><b>true</b></td></tr><tr><td>123</td><td>false</td><td><b>true</b></td><td><b>true</b></td><td><b>true</b></td></tr><tr><td>string</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>object</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>[]</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>[string, number]</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>any</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>void</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>null</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>undefined</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>never</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>"string literal" | 123</td><td>false</td><td>false</td><td>false</td><td><b>true</b></td></tr><tr><td>"string literal" | string</td><td>false</td><td>false</td><td>false</td><td>false</td></tr><tr><td>123 | number</td><td>false</td><td>false</td><td>false</td><td>false</td></tr></tbody></table>


...几个小时后,我终于做到了这一点。 你4分钟前提出了问题,然后在同一分钟回答了它? - Johan
1
@Johan 当我在玩Jeopardy!游戏的时候,我会全力以赴 :) https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/ - FrogTheFrog
1
谢谢,我只是有点困惑,不知道格式的问题。现在我知道我没有穿越时空 :) - Johan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接