Intentando obtener la siguiente expresión de tipo TypeScript correcta para tener un local Intentando escribir una función inteligente de tipo llamada getValue(string, defValue?) que devuelve una cadena o el valor predeterminado si no se encuentra la clave. La función debe tener el tipo de string | typeof defaultValue la función lookupValue () tiene la escritura correcta para admitir esto.

En este punto, he probado 4 variaciones diferentes del enfoque, tres de las cuales fallan en la compilación o el uso, el último caso no maneja completamente las entradas de tipo pero compila.

//  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");

}

El objetivo es tener algo que cumpla con este requisito:

    const big = getvalue("testBig");        // Should be type of string | undefined
    const bigDef = getvalue("testBig", "something");        // Should be type string
1
koblas 5 feb. 2019 a las 19:24

2 respuestas

La mejor respuesta

Mecanografiado tiene limitaciones cuando se trata de argumentos opcionales y genéricos en este momento. Si agrega un parámetro opcional mezclado con un genérico, el compilador siempre asume <generic> | undefined. p.ej:

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

En este caso, param1 es "A | undefined". Esto arruina cualquier tipo de flujo porque el aspecto genérico no incluye el aspecto indefinido, sino que se agrega, incluso en los casos en los que llama a doSomething() sin argumento. Entonces, el resultado de doSomething ("gelatina") es string | undefined; lo cual es una tontería porque uno pensaría que sería una cuerda. Este también es el caso de doSomething() que tiene el tipo de retorno {} | undefined en lugar de solo undefined.

A veces puedes solucionar esto golpeando Typecript muy fuerte con un gran martillo metafórico. Por lo general, el martillo consiste en elaborar las definiciones de tipo correctas y luego lanzar todo lo que se queja. No hay una talla única para todos, pero en su caso puede hacer:

  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;

Primero estableces R y A en cadena e indefinido por defecto. Esto es para que el compilador asuma que R / A es una cadena / indefinido si no tiene otra información para continuar. Entonces tienes que configurar defvalue: A para evitar que el compilador agregue un | undefined. Sin | undefined el compilador puede hacer álgebra de tipos. Luego tienes que especificar que el resultado de la función es R | A porque eso es básicamente lo que quieres. El siguiente paso es decirle al compilador que deje de inferir lo que "A" se basa en el resultado de la llamada a lookupValue porque inferirá incorrectamente. Esta es también la razón por la que necesitamos usar "R" en lugar de solo string | A. Básicamente, si no pone en mayúsculas el resultado de lookupValue (o usa cadena | A como tipo de resultado), el compilador es lo suficientemente inteligente como para ver que no hay suficiente información de tipo, por lo que "A" es "indefinido" o "cadena" dependiendo de en qué introduces el resultado y no se compila (si omites la conversión) o falla como se muestra a continuación si el tipo de retorno de getValue se establece en string | A:

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

"A" inferirá una cadena que es incorrecta porque debería ser un error de compilación "" no se puede asignar una cadena | undefined to string ". El otro caso:

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

A inferirá a undefined lo que significa que const result será del tipo string | undefined que también es incorrecto.

Para evitar lo anterior, agregamos as unknown) as R | A en la segunda línea para obtener:

 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;

Funciona correctamente para todos los casos en los que puedo pensar.

// 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");
2
Andrew Trumper 5 feb. 2019 a las 18:55

Podría implementar un argumento opcional dinámicamente algo como esto:

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
2
cutscenedev 7 nov. 2019 a las 23:13