【问题标题】:How to force a type to be defined by the given argument如何强制由给定参数定义类型
【发布时间】:2022-07-29 20:11:13
【问题描述】:

背景情况:

// Type to do the validation - not so important.
type Validate<N, S> = [S] extends [N] ? N : never;

// Note that with below line, N will have a circular constraint when using from validateName().
// type Validate<N, S> = S extends N ? N : never;

// The function to validate - how it runs as JS (or even what it returns) is not important.
// .. However, it is important that it must use a Validate type with two arguments like above.
function validateName<N extends string, S extends Validate<N, S>>(input: S) {}

问题: 如何只补充N而不是S到上面的validateName(或Validate)?我们希望 S 保持由实际参数推断。

// Test.
type ValidNames = "bold" | "italic";

// Desired usage:
// .. But we can't do this due to "Expected 2 type arguments, but got 1."
validateName<ValidNames>("bold");   // Ok.
validateName<ValidNames>("bald");   // Error.

// Cannot solve like below due to: "Type parameter defaults can only reference previously declared type parameters."
function validateName<N extends string, S extends Validate<N, S> = Validate<N, S>>(input: S) {}

解决方法:

解决方法 #1:将输入存储为变量,并使用其类型。

const input1 = "bold";
const input2 = "bald";
validateName<ValidNames, typeof input1>(input1);  // Ok.
validateName<ValidNames, typeof input2>(input2);  // Error.

解决方法 #2:使函数需要额外的参数。

function validateNameWith<N extends string, S extends Validate<N, S>>(_valid: N, input: S) {}
validateNameWith("" as ValidNames, "bold");  // Ok.
validateNameWith("" as ValidNames, "bald");  // Error.

解决方法 #3:使用闭包 - 将函数包装在另一个函数中。

// First a function to create a validator and put N into it.
function createValidator<N extends string>() {
    // Return the actual validator.
    return function validateName<S extends Validate<N, S>>(input: S) {}
}
const validateMyName = createValidator<ValidNames>();
validateMyName("bold");  // Ok.
validateMyName("bald");  // Error.

编辑:修改了上面的函数,去掉了令人困惑的:N[]返回部分。

更多信息/背景

我实际上是在尝试构建一个可以使用的字符串验证器,例如。用于 html 类名。其他一切都有效,除了用法很笨拙(请参阅上面的 3 个解决方法)。

// Thanks to: https://github.com/microsoft/TypeScript/pull/40336
export type Split<S extends string, D extends string> =
    string extends S ? string[] :
    S extends '' ? [] :
    S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
    [S];

// Type to validate a class name.
type ClassNameValidator<N extends string, S extends string, R = string> =
    Split<S, " "> extends N[] ? R : never;

// Function to validate class.
function validateClass<N extends string, S extends ClassNameValidator<N, S>>(input: S) {}

const test3 = "bold italic";
const test4 = "bald";
validateClass<ValidNames, typeof test3>(test3);  // Ok.
validateClass<ValidNames, typeof test4>(test4);  // Error.

【问题讨论】:

    标签: typescript


    【解决方案1】:

    我可能有适合您的解决方案。除了Validation 类型之外,您还可以使用一种类型来根据给定的string union 计算可能值的所有排列。

    type AllPermutations<T extends string> = {
      [K in T]: 
        | `${K}${AllPermutations<Exclude<T, K>> extends infer U extends string 
            ? [U] extends [never] 
              ? "" 
              : ` ${U}` 
            : ""}` 
        | `${AllPermutations<Exclude<T, K>> extends infer U extends string 
            ? U 
            : never}`
    }[T]
    
    // Function to validate class.
    function validateClass<N extends string>(input: AllPermutations<N>) {}
    

    它通过了以下测试。

    type ValidNames = "bold" | "italic";
    
    validateClass<ValidNames>("bold");  // Ok.
    validateClass<ValidNames>("bold italic");  // Ok.
    validateClass<ValidNames>("italic");  // Ok.
    validateClass<ValidNames>("italic bold");  // Ok.
    validateClass<ValidNames>("something else");  // Error.
    

    但如果工会变得更大,这将变得渴望绩效。如果ValidNames 是一个更大的联合体,我不建议使用它。

    Playground

    【讨论】:

    • 不,它不能解决它。我需要使用 SValidate&lt;N, S&gt; 的形式进行验证。我不想让示例/情况进一步复杂化,但可能会添加更多背景信息。 (我实际上是用它来拆分 TS 中的字符串,例如“粗斜体”,我有一个类型可以做到这一点 - 但它与手头的问题无关。)
    • @takaturre 请编辑您的问题以包含您的真实示例。否则很难帮你。
    • 我进行了编辑以提供更大的上下文。但是,它不会改变核心问题/问题 - 当然,这有助于理解,也许有人甚至会想出开箱即用的替代方案。
    • 好的,这好多了,谢谢!但是我想澄清一件事(因为我只是在学习 TS):这实际上是创建所有那些有效的字符串,比如“粗斜体”和“斜体粗体”,还是只是创建一种“动态”类型来匹配?因为我最初考虑过这种方法,并得出结论,这很快就会变得非常沉重。例如,考虑拥有 100 个类名,然后创建所有这些排列......
    • 有一天,当 TypeScript 获得部分类型推断时,这可能会成为可能。 github.com/microsoft/TypeScript/issues/26242
    猜你喜欢
    • 2021-10-30
    • 2023-03-28
    • 2019-02-25
    • 2012-02-14
    • 1970-01-01
    • 2018-01-28
    • 2017-04-13
    • 2018-11-29
    • 1970-01-01
    相关资源
    最近更新 更多