无法在TypeScript中使用可选参数创建动态类型

时间:2019-02-05 16:24:57

标签: typescript

尝试正确获取以下TypeScript类型表达式以使其具有本地 尝试编写一个名为getValue(string, defValue?)的类型智能函数,如果找不到该密钥,则该函数将返回字符串或默认值。该函数的类型应为string | typeof defaultValue,lookupValue()函数应具有正确的类型以支持此功能。

在这一点上,尝试了4种不同的方法,其中三种在编译或使用中失败,最后一种情况不能完全处理类型输入,但可以编译。

//  This function is good -- correctly handling the types
lookupValue<D>(record: string[], key: string, defvalue: D): D | string {
  const index = this.columnHeaders[key];
  const v = index !== undefined ? record[index] : undefined;

  return v === undefined || v === "" ? defvalue : v.trim();
}

someFunction(record: string[]) {

    // -- Test 1
    const getValue = <T>(key: keyof typeof COLUMNS, defvalue = undefined) => lookupValue(record, key, defvalue);

    //   Argument of type '""' is not assignable to parameter of type 'undefined'.
    const bigDef = getvalue("testBig", "something");

    // -- Test 2

    // Type 'undefined' is not assignable to type 'T'.
    const getValue = <T>(key: keyof typeof COLUMNS, defvalue: T = undefined) => lookupValue(record, key, defvalue);

    // -- Test 3

    // Won't compile since the defvalue is "T | undefined" which isn't valid
    const getValue = <T>(key: keyof typeof COLUMNS, defvalue?: T) => lookupValue(record, key, defvalue);

    // -- Test 4

    // Compiles but is wrong since the following getValue "works"
    const getValue = <T = undefined>(key: keyof typeof COLUMNS, defvalue?: T) => lookupValue(record, key, defvalue as T);

    //  Works - but shouldn't
    const foo: string = getValue("test");

}

目标是要满足以下要求:

    const big = getvalue("testBig");        // Should be type of string | undefined
    const bigDef = getvalue("testBig", "something");        // Should be type string

2 个答案:

答案 0 :(得分:1)

Typescript目前在可选参数和泛型方面有局限性。如果您添加了一个与泛型混合使用的可选参数,则编译器将始终假设<generic> | undefined。例如:

function doSomething<A>(param1?: A) {
  return param1;
}

在这种情况下,param1为“ A | undefined”。这会破坏所有类型的流程,因为通用方面不包含未定义方面,而是坚持使用了它-即使在您不带参数调用doSomething()的情况下也是如此。所以doSomething(“ jello”)的结果是string | undefined;这很愚蠢,因为人们会认为这将是字符串。对于doSomething(),返回类型为{} | undefined而不是undefined的情况也是如此。

有时,您可以通过用大型隐喻锤子严重击打Typescript来解决此问题。通常,锤子包括制定正确的类型定义,然后投射所有抱怨的内容。没有一种适合所有人的尺码,但您可以这样做:

  const getValue = <R = string, A = undefined>(key: string, defvalue: A = (undefined as unknown) as A): R | A =>
    (lookupValue(record, key, defvalue) as unknown) as R | A;

首先,将R和A设置为字符串,并且默认情况下未定义。这样一来,如果编译器没有其他可用信息,则假定R / A是字符串/未定义。然后,必须设置defvalue: A来阻止编译器添加| undefined。如果没有| undefined,则编译器可以键入代数。然后,您必须指定函数的结果为R | A,因为这基本上就是您想要的。下一步是告诉编译器停止基于对lookupValue的调用结果来推断“ A”是什么,因为它将错误地进行推断。这也是为什么我们需要使用“ R”而不是string | A的原因。本质上,如果您不区分lookupValue的结果(或使用字符串| A作为结果类型),则编译器足够聪明,可以看到类型信息不足,因此,根据情况,“ A”为“未定义”或“字符串”如果将getValue的返回类型设置为string | A,则将结果插入到结果中并不能编译(如果省略了强制转换)或失败,如下所示:

const result: string = getValue("testBig");

“ A”会推断出错误的字符串,因为它应该是编译错误。“”无法分配字符串|未定义为字符串”。另一种情况:

const result: string = getValue("testBig");

A会推断为undefined,这意味着const result的类型为string | undefined,这也是错误的。

为避免上述情况,我们在第二行添加as unknown) as R | A以获得:

 const getValue = <R = string, A = undefined>(key: string, defvalue: A = (undefined as unknown) as A): R | A =>
    (lookupValue(record, key, defvalue) as unknown) as R | A;

我能想到的所有情况下都能正常工作

// ss is number | string
      const ss = getValue("testBig", 1);

// bigDef is string
      const bigDef = getValue("testBig", "something");

// sdf is string | undefined
      const sdf = getValue("testBig", undefined);
      const sdf = getValue("testBig");

// Compile error -> string | undefined can't be assigned to string
      const asdfa: string = getValue("testBig");

答案 1 :(得分:0)

您可以实现如下所示的动态可选参数:

function doSomething<T extends keyof TypeDataSchema>(
  type: T,
  ...[data]: TypeDataSchema[T] extends never ? [] : [TypeDataSchema[T]]
) {

}

interface TypeDataSchema {
  'hello': number
  'bye': never
}

doSomething('bye') // ok
doSomething('bye', 25) // expected 1 argument but got 2
doSomething('hello', 5) // ok
doSomething('hello') // expected 2 argument but got 1