reactjs - Typescript: Cómo usar un array de opciones como tipo para otra propiedad
Estoy intentando encontrar una manera de hacer cumplir un tipo de propiedad usando los valores de otra propiedad.
La lista de opciones del arreglo podría ser cualquier cosa establecida cuando se use el componente, así que no puedo escribir manualmente las opciones. ¿Es esto siquiera posible?
// Props
export interface Props {
options: string[]; // These will be the options
selected: string; // Enforce this to be one the options
}
// Component
const Component = ({ options, selected }: Props) => {
...
return <div>{selected}</div>;
};
// Usage of component somewhere else
<Component
options={['2020', '2021', '2022', '2023']}
selected="1999" // <--- This should return an error
/>
Intenté usar genéricos, pero soy bastante nuevo en Typescript y fallé :(
Respuesta #1
- Define
optionscomo una tupla usando el operador spread (gracias a Tobias S por sugerir esto). Esto permite usar el array de cadenas como una lista de tipos literales. - Usa
numberpara indexar el array y crear una restricción para la propiedadselected(una unión de todos los valores del array).
// Props
export interface Props<T extends string[]> {
options: [...T];
selected: T[number];
}
// Component
const Component = <T extends string[]>(props: Props<T>) => {
return <div>{props.selected}</div>;
};
<Component
options={['2020', '2021', '2022', '2023']}
selected="1999"
></Component>
Área de juegos de Typescript.
Respuesta #2
Conceptualmente, necesitas que Props sea genérico en la unión de tipos literales de cadena de los elementos del array options:
interface Props<T extends string> {
options: T[];
selected: T
}
Un inconveniente es que en tu componente, el compilador inferirá con gusto T tanto de las propiedades options como selected, evitando que ocurra el error que deseas:
const Component = <T extends string>({ options, selected }: Props<T>) => {
return <div>{selected}</div>;
};
<Component
options={['2020', '2021', '2022', '2023']}
selected="2019" // no error
></Component>
// Props<"2020" | "2021" | "2022" | "2023" | "2019"> inferred 😢
Solo quieres verificar la propiedad selected contra cualquiera que T se haya inferido de options. En otras palabras, quieres que el T en la propiedad selected sea un "uso no inferencial" del parámetro de tipo, como se describe en microsoft/TypeScript#14829.
Aunque no existe un soporte directo para esto, hay varias formas de lograr tal efecto. Una forma es agregar un segundo parámetro de tipo limitado por el primero, de manera que selected no afecte a T directamente:
interface Props<T extends string, U extends T> {
options: T[];
selected: U;
}
const Component = <T extends string, U extends T>({ options, selected }: Props<T, U>) => {
return <div>{selected}</div>;
};
Vamos a intentarlo:
<Component
options={['2020', '2021', '2022', '2023']}
selected="2019" // error 👍
></Component>
<Component
options={['2020', '2021', '2022', '2023']}
selected="2022" // okay 👍
></Component>
Se ve bien. Ahora selected se verifica contra los elementos de options.
Enlace de Playground al código
