简体   繁体   English

typescript 中的条件类型参数

[英]conditional types parameters in typescript

I'm attempting to create a function where the columns parameter depends on what type has been selected.我正在尝试创建一个 function ,其中columns参数取决于选择的type

function useNavigation(type: "horizontal" | "grid" | "vertical", columns?: number)

How to change that type declaration to set that columns parameter is required when parameter type is grid当参数typegrid时,如何更改该类型声明以设置该列参数是required

You can use function overloads to achieve this.您可以使用 function 重载来实现此目的。 In my example below, the first two declarations are specifying different arguments based on your requirements whereas the last declaration (and consequently the implementation) receives all possible arguments, which is why type is still 'horizontal' | 'vertical' | 'grid'在下面的示例中,前两个声明根据您的要求指定不同的 arguments,而最后一个声明(以及因此实现)接收所有可能的 arguments,这就是为什么type仍然是'horizontal' | 'vertical' | 'grid' 'horizontal' | 'vertical' | 'grid' 'horizontal' | 'vertical' | 'grid' . 'horizontal' | 'vertical' | 'grid'

The caller will get proper types and auto-completion, you'll simply have to do the checks inside the function to determine your logic based on the arguments.调用者将获得正确的类型和自动完成功能,您只需在 function 中进行检查,即可根据 arguments 确定您的逻辑。

function useNavigation(type: 'horizontal' | 'vertical');
function useNavigation(type: 'grid', columns: number);
function useNavigation(
  type: 'horizontal' | 'vertical' | 'grid',
  columns?: number,
) {
  // Your function body...
}

Here's a TS playground demonstrating this.这是一个展示这一点的 TS 操场

Traditionally (pre TS 2.8 or TS 3.0) you could only use overloads to achieve this, but now I'd be inclined to use a tuple-typed rest parameter ;传统上(TS 2.8 或 TS 3.0 之前)你只能使用重载来实现这一点,但现在我倾向于使用元组类型的 rest 参数 specifically a union of such tuple types:特别是这种元组类型的联合

function useNavigation(...args:
    [type: "horizontal" | "vertical", columns?: number] |
    [type: "grid", columns: number]
) { }

That means the arguments to useNavigation() must either be: a pair where the first element is "horizontal" or "vertical" and the second element is an optional number value;这意味着使用useNavigation()的 arguments 必须是:第一个元素是"horizontal""vertical"并且第二个元素是可选number的一对; or a pair where the first element is "grid" and the second element is a required number value.第一个元素是"grid"而第二个元素是必需number的一对。

You can test that it works as desired:您可以测试它是否按需要工作:

useNavigation("horizontal") // okay
useNavigation("vertical", 20); // okay
useNavigation("grid", 10); //okay
useNavigation("grid"); // error!
// ---------> ~~~~~~
// Source has 1 element(s) but target requires 2

Note that the type signature for useNavigation() is also taking advantage of labeled tuple elements , to show that we intend the caller to think of the first argument as having a name of type and the second argument as having a name of columns .请注意, useNavigation()的类型签名也利用了标记的元组元素,以表明我们希望调用者将第一个参数视为具有type名称,而将第二个参数视为具有columns名称。 Such labels are only relevant when it comes to type hints in IntelliSense and do not affect the types or runtime behavior.此类标签仅在涉及 IntelliSense 中的类型提示时才相关,并且不会影响类型或运行时行为。 In particular, there is no variable named type or columns here;特别是,这里没有名为typecolumns的变量; the function implementation sees only an args array: function 实现只看到一个args数组:

function useNavigation(...args:
    [type: "horizontal" | "vertical", columns?: number] |
    [type: "grid", columns: number]
) {    
    if (args[0] === "grid") {
        args[1].toFixed(); // okay
    } 
}

You could, if you want, destructure args into type and columns variables as in如果需要,您可以将args解构为typecolumns变量,如

const [type, columns] = args;

but there is an advantage to leaving it as args ;但是将其保留为args有一个优势; namely, that the compiler sees args as a discriminated union type.即,编译器将args视为可区分的联合类型。 Note that in the above implementation, the compiler sees that if args[0] === "grid" , then args[1] is definitely a number and not possibly undefined .请注意,在上面的实现中,编译器看到如果args[0] === "grid" ,那么args[1]肯定是一个number ,而不可能是undefined Compare to the behavior when you separate args out into two no-longer-seen-as-correlated variables:与将args分成两个不再被视为相关变量时的行为进行比较:

if (type === "grid") {
    columns.toFixed(); // oops, possibly undefined
}

The compiler only sees columns as type number | undefined编译器仅将columns视为类型number | undefined number | undefined regardless of whether or not you check type === "grid" first.无论您是否先检查type === "grid"都是number | undefined的。 This might not be a big deal to you, but discriminated unions are useful enough that I wanted to point it out.这对您来说可能没什么大不了的,但受歧视的工会非常有用,我想指出这一点。

Playground link to code Playground 代码链接

My favorite pattern to achieve some additional parameters to be required for some parameter(s) is to wrap it in an object and type it as:我最喜欢的实现某些参数所需的一些附加参数的模式是将其包装在 object 中并将其键入为:

function useNavigation(options: { type: "horizontal" | "vertical"; columns?: number } | { type: "grid", columns: number })

and use it as并将其用作

useNavigation({ type: "horizontal" })
useNavigation({ type: "grid", columns: 12 })

It may feel a little bit repetitive but provides good typesafely, also when types are narrowed down after property check它可能感觉有点重复,但提供了良好的类型安全性,在属性检查后缩小类型时也是如此

if (options.type === "grid") {
 // here typescript knows that `columns` exists
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM