Typescript: Solución rápida para 'las uniones no se pueden usar en firmas de índice, use un tipo de objeto mapeado en su lugar'

Creado en 17 may. 2018  ·  37Comentarios  ·  Fuente: microsoft/TypeScript

El siguiente código:

type K = "foo" | "bar";

interface SomeType {
    [prop: K]: any;
}

Da este mensaje de error:

An index signature parameter type cannot be a union type. Consider using a mapped object type instead.

Nadie sabe qué tipos de objetos mapeados son, así que démosle una solución rápida

  • Cambia la firma del índice a un tipo mapeado
  • Mueve otros miembros a un tipo de objeto separado que se combina con un tipo de intersección
  • Cambia el tipo de objeto contenedor a un alias de tipo si el tipo de objeto contenedor es una interfaz
  • Se cruza con todas las cláusulas extends si el tipo de objeto contenedor es una interfaz y tiene cláusulas extends
Error Messages Quick Fixes Moderate Fixed Suggestion help wanted

Comentario más útil

Puedes hacerlo:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Aunque Bar no tiene firma de índice (es decir, no puede hacer (obj as Bar)[value as Foo] ).

Editar: Aunque si pudiera hacer que la advertencia no sea un problema, ¡estaría eternamente agradecido!

Todos 37 comentarios

Nadie sabe qué tipos de objetos mapeados son, así que démosle una solución rápida

+1, solo vine aquí porque esperaba que 2.9 apoyara a los sindicatos como firmas de índice según su código de ejemplo. Creo que esta ha sido una característica deseada desde hace mucho tiempo: # 5683, # 16760, etc.

Puedes hacerlo:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Aunque Bar no tiene firma de índice (es decir, no puede hacer (obj as Bar)[value as Foo] ).

Editar: Aunque si pudiera hacer que la advertencia no sea un problema, ¡estaría eternamente agradecido!

me gustaría trabajar en esto: riendo:

Mueve otros miembros a un tipo de objeto separado que se combina con un tipo de intersección

¿Qué debemos hacer si el tipo de objeto que contiene es una clase?
Solo puedo imaginar que es una interfaz

Entonces, ¿qué debería hacer el código siguiente después de la corrección rápida?

type K = "1" | "2"

class SomeType {
    a = 1;
    [prop: K]: any;
}

Entonces, ¿qué debería hacer el código siguiente después de la corrección rápida?

Yo diría que esto no debería ser reparable.

@mhegazy Estoy usando 3.0.0-rc y sigo recibiendo el mismo error que se publicó originalmente. ¿Es esto esperado?

Estoy usando 3.0.0-rc y sigo recibiendo el mismo error que se publicó originalmente. ¿Es esto esperado?

si. el error es correcto. este problema se rastreaba agregando una solución rápida para él, es decir, la pulpa ligera al lado del mensaje de error.

no hay acciones de código disponibles con 2.9.1 y vscode

@ThaJay No configurar una versión más nueva.

Obviamente. Lamento no haber revisado la línea de tiempo primero, solo asumí que sería lo suficientemente nueva. Nuevo en ts. Verificará con la versión 3.

cómo describir esto:

function createRequestTypes(base){
  return ['REQUEST', 'SUCCESS', 'FAILURE'].reduce((acc, type) => {
    acc[type] = `${base}_${type}`
    return acc
  }, {})
}

const user = createRequestTypes('USER')
console.log(user.REQUEST) // error
// just string? like:
interface IRequestType: {[key: string]: string}

Intenté a continuación, todo falló:

type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = {
  [key in requestStatus]: string
}
// or
interface IRequestTypes {[key: keyType]: string}
// or even
type requestTypes = {
  FAILURE: string,
  SUCCESS: string,
  REQUEST: string
}

@maicWorkGithub aquí tienes:

const user = createRequestTypes('USER')
console.log(user.REQUEST) 

function createRequestTypes(base:string):requestTypes {
  const result : requestTypes    = {}
  const arr    : requestStatus[] = ['REQUEST', 'SUCCESS', 'FAILURE']  

  return arr.reduce((acc, type) => {
    acc[type] = `${base}_${type}`
    return acc
  }, result)
}


type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = { [key in requestStatus]?: string }

@ihorskyi ¡¡Gracias !!

Solo tengo curiosidad por saber por qué type funciona, pero interface no. ¿Alguien puede explicarme, por favor? ¿Cuál es el motivo de tal limitación (o característica?) De interface .

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any}; // ok
interface Baz {[key in Foo]: any} // =>

// A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)
// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
// 'Foo' only refers to a type, but is being used as a value here.ts(2693)

Esta fue una solución automática increíble para descubrir. ¡Gracias por implementarlo! :)

Lo mismo para las clases.

Puedes hacerlo:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Aunque Bar no tiene firma de índice (es decir, no puede hacer (obj as Bar)[value as Foo] ).

Editar: Aunque si pudiera hacer que la advertencia no sea un problema, ¡estaría eternamente agradecido!

¡Utilice Record lugar!

type Foo = 'a' | 'b'
type Bar = Record<Foo, any>

Para agregar un ejemplo más de esto usando una clase ...

class Foo {
   a: string;
   b: string;
}

type Bar = {[key in keyof Foo]: any};

es incluso mejor cuando se usa Partial

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

Nadie sabe qué tipos de objetos mapeados son, así que démosle una solución rápida

@DanielRosenwasser
¿Por qué el mensaje de error no puede sugerir la respuesta, por ejemplo, mostrar un ejemplo rápido usando tipo mapeado? Eso sería solo un par de líneas de código que serán consistentes con la longitud promedio de los mensajes de error de TypeScript: trollface:

¿Alguien sabe si es posible decir que la interfaz que usa el tipo o enum como clave puede aceptar solo una propiedad?

Por ejemplo, una firma como esta: { <field>: { <and|or|xor>: <int> } } tomada del operador mongo bit a bit .

export enum BitwiseOperator {
    and = "and",
    or = "or",
    xor = "xor",
}

export type BitwiseCondition = {
    [key in BitwiseOperator]?: number;
}

Luego, al usarlo, me gustaría validar que la variable definida por la interfaz tiene solo una propiedad.

const query: BitwiseCondition = {
  and: 5,
  or: 6  // raise a ts error
};

@ b4dnewz No puede hacerlo en Typecript. Solución alternativa: https://github.com/Microsoft/TypeScript/issues/10575

@ b4dnewz , si solo quiere 1 propiedad, ¿por qué no hacerlo así?

export enum BitwiseOperator {
  and = "and",
  or = "or",
  xor = "xor",
}

export type BitwiseCondition = {
  operator: BitwiseOperator;
  value: number;
}

@benwinding desafortunadamente, la forma devuelta es diferente de lo que espera mongodb

@apieceofbart gracias por la sugerencia, lo he

Estoy tratando de mantener las definiciones de los mongo-operadores lo más simples posible para evitarme dolores de cabeza 😁 tal vez en el futuro se agregue un soporte adecuado

@ b4dnewz bastante justo,

Quizás una opción más simple que pueda usar es:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

Eso es lo más cerca que obtendrás sin demasiada duplicación

@ b4dnewz bastante justo,

Quizás una opción más simple que pueda usar es:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

Eso es lo más cerca que obtendrás sin demasiada duplicación

Esto no producirá errores en este ejemplo:

const query: BitwiseCondition = {
  and: 5,
  or: 6  // raise a ts error
};

Pensé que ese es el punto

@apieceofbart ,

Esto no producirá errores en este ejemplo:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

const query: BitwiseCondition = {
  and: 5,
  or: 6  // doesn't raise a ts error!
};

¡Woah! eso es raro: open_mouth: ¡No sabía eso!

Parece que Typecript no admite tipos de objetos mutuamente excluyentes. También fue propuesta para el lenguaje aquí: https://github.com/microsoft/TypeScript/issues/14094

Aunque todavía es técnicamente posible ...

A partir de esta respuesta de tipos condicionales (los tipos más difíciles), pero no es bonito ...

/*
 XOR boiler plate
*/
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object
  ? (Without<T, U> & U) | (Without<U, T> & T)
  : T | U;
type XOR3<S, T, U> = XOR<S, XOR<T, U>>;

// Code start
export type BitwiseCondition = XOR3<
  { or: number },
  { xor: number },
  { and: number }
>;

const query1: BitwiseCondition = {
  and: 5
};

const query: BitwiseCondition = {
  and: 5,
  or: 6 // raise a ts error
};

Si alguien puede hacer esto más bonito o mejor, por favor hágalo

@mvasin FWIW, esto _ parece_ lograr el mismo resultado, pero estoy completamente de acuerdo en que debería ser una característica de las interfaces tal como lo es en los tipos.

type Foo = 'a' | 'b';

type Bar = {
  [key in Foo]: any
}

interface A extends Bar { }

class Wol implements A{
  a: any;
  b: any;
}

Para mecanografiado 3.5, parece que tengo que hacer esto:

export interface DataTableState {
  columnStats: {[key in keyof DataTable]?:{}}
}

¿Es ésta la mejor manera de hacer ésto?

¿Por qué exactamente una firma de índice no puede usar un tipo de enumeración? El tipo mapeado casi hace lo que quiero, pero luego TypeScript espera que cada cadena de la enumeración exista como una clave definida. En realidad, no quiero afirmar que existan todas las claves, más que si existen claves, deben vivir en la enumeración.

Por ejemplo para el tipo:

type MyType = {
  [Key: 'foo' | 'bar' | 'zip']: number;
};

Esto debería satisfacer:

const x: MyType = {
  foo: 1,
  zip: 2
};

Si bien podría establecer las otras claves como indefinidas para un tipo mapeado, prefiero que las claves sean opcionales, pero si están presentes, el valor no puede ser indefinido. Si hago que los valores de tipo mapeados sean opcionales, el código funciona pero los tipos son menos fuertes.

es incluso mejor cuando se usa Partial

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

¡Gracias!
Útil cuando necesita definir un tipo que coincide parcialmente con un diccionario

"Parcial" también se puede utilizar en registros:

type Foo = 'a' | 'b';
let foo1: Record<Foo, number> = { a: 1, b: 2 };
let foo2: Partial<Record<Foo, number>> = { a: 1 };

Me encuentro visitando sin saberlo esta página de GitHub aproximadamente cada mes.

Mi último es realmente simple:

interface ObjectLiteral {
    [key: string | number]: any
}
export const mapToObjectLiteral = (map: Map<string|number, any>) =>
    Array.from(map).reduce((objLit, [key, value]) => {
        objLit[key] = value
        return objLit
    }, {} as ObjectLiteral)

image

Puedo desplazarme hacia arriba y encontrar una solución alternativa, pero solo quería proporcionar comentarios de que este problema ocurre con frecuencia en el trabajo diario en escenarios ligeramente diferentes.

Aquí hay un ejemplo:

type MapKey = string | number;
type ObjectLiteral<T extends MapKey, V = any> = {
  [P in T extends number ? string : T]: V;
};

export const mapToObjectLiteral = <T extends MapKey, V>(map: Map<T, V>) =>
  Array.from(map).reduce((objLit, [key, value]) => {
    objLit[key as keyof ObjectLiteral<T>] = value;
    return objLit;
  }, {} as ObjectLiteral<T, V>);

// how to make a better type of map ?
const m = new Map<1 | "foo", "a" | "b">();
m.set(1, "a");
m.set("foo", "b");

const o = mapToObjectLiteral(new Map(m));

console.log(o[1], o.foo); // just got an union type of every member of 'o'

https://github.com/microsoft/TypeScript/issues/24220#issuecomment -504285702

Para agregar un ejemplo más de esto usando una clase ...

class Foo {
   a: string;
   b: string;
}

type Bar = {[key in keyof Foo]: any};

Muy útil. ¡Gracias! 🚀

¿Fue útil esta página
0 / 5 - 0 calificaciones