Estoy escribiendo un componente React que envuelve el componente Select de react-select, que tiene una declaración de propiedad que comienza así:

export type SelectComponentsProps = { [key in string]: any };

export interface Props<OptionType extends OptionTypeBase = { label: string; value: string }> extends SelectComponentsProps {

Importé este tipo y traté de declarar los accesorios de mi componente contenedor de esta manera:

import { Props as SelectProps } from "react-select/src/Select";
type Props = Omit<SelectProps, "inputId"> & { ... some other stuff };

Pero tuve un problema en el que podía pasar "de forma segura" cualquier cosa que quisiera a mi componente, incluso para campos que tenían declaraciones de tipo explícitas en SelectProps:

// okay
<MySelectWrapper onChange="definitely not a function"/>

// not okay, fails type checking
<Select onChange="definitely not a function"/>

Después de investigar un poco, descubrí que la combinación de Omit y una firma de índice (en el ejemplo react-select: SelectComponentsProps) hace que el compilador elimine los campos especificados explícitamente y, en su lugar, solo use la firma de índice.

interface ArbitraryKeyable {
  foo: number;
  [key: string]: any;
}

const value = {
  foo: 'not a number',
  bar: 'this can be anything'
}

// Fails: foo is the wrong type.
const arbitrary: ArbitraryKeyable = value;

type OmittedArbitraryKeyable = Omit<ArbitraryKeyable, 'this is not a field'>;

// Succeeds, even though the type should functionally be the same.
const omittedArbitrary: OmittedArbitraryKeyable = value;

(enlace del patio de recreo)

Esto es lo que el patio de recreo cree que es ese tipo:

enter image description here

¡Por supuesto que acepta todo! ¿Hay alguna forma de definir una versión de Omit que conserve los campos definidos explícitamente? Alternativamente, ¿hay alguna operación de tipo que pueda realizar para eliminar la firma de índice y solo tener los campos explícitos? (Realmente no lo necesito para mi caso de uso, por lo que estaría bien perder esa flexibilidad para obtener seguridad de tipos en otro lugar).

0
stuffy 4 oct. 2019 a las 23:33

1 respuesta

La mejor respuesta

Omit en combinación con una firma de índice no funcionará, porque utiliza internamente el operador keyof. Cuando aplica keyof a un tipo con una firma de índice, solo devolverá el tipo de índice y no incluirá miembros explícitos:

interface ArbitraryKeyable {
  foo: number;
  [key: string]: any;
}

type Keys = keyof ArbitraryKeyable // type Keys = string | number (no "foo")

Y excluir una propiedad "foo" de una firma de índice de cadena aún conserva el índice.

type S1 = Omit<ArbitraryKeyable, 'foo'>
type S2 = Pick<ArbitraryKeyable, Exclude<keyof ArbitraryKeyable, "foo">>;
type S3 = Pick<ArbitraryKeyable, Exclude<string| number, "foo">>; 
type S4 = Pick<ArbitraryKeyable, string | number>; // { [x: string]: any; [x: number]: any;}

Existe una forma de eliminar el índice firma, pero se siente un poco hack, si me preguntas. Probablemente sea mejor elegir explícitamente todas las propiedades deseadas, cuando corresponda. Eso también tiene la ventaja de que puede controlar y reducir mejor la API externa.

// instead of omit...
type Props = Omit<SelectProps, "inputId"> & { ... some other stuff }

// ... pick the props
type Props = Pick<SelectProps, "prop1" | "prop2">
1
ford04 5 oct. 2019 a las 09:41