Creé un componente Select
en reaccionar que está completamente tipeado y ahora le agregué un accesorio multiple
, que cambiará la escritura del valor y la devolución de llamada onChange
para que sea del tipo Array<T>
Lo que hace el accesorio multiple
es que usa lo que Creo que se llama Condicional distributivo para determinar el tipo de value
y onChange
de los accesorios del Componente de esta manera:
interface SelectBaseProps<T extends SelectValue = string> { options: SelectOption<T>[]; placeholder?: string; disabled?: boolean; className?: string; searchable?: boolean; } export type SelectProps<T extends SelectValue, TMultiple extends boolean = false> = SelectBaseProps<T> & (TMultiple extends false ? { multiple?: TMultiple; value: T; onChange: (value: T) => void; } : { multiple?: TMultiple; value: T[]; onChange: (value: T[]) => void; });
Donde SelectValue
solo limita los valores para que sean de tipo cadena o número por ahora. Lo sé, no es la implementación más bonita, pero esto ya es después de algunas iteraciones de depuración. Y luego, el componente de selección en sí es básicamente solo
export default function Select<T extends SelectValue, TMultiple extends boolean = false>(...) {...}
Ahora, a primera vista, ¡esto parece funcionar bien! Si uso esto en un componente de prueba como este:
function SelectTest() { const [val, setVal] = useState<string>(); const options: SelectOption<string>[] = [ { label: 'Option 1', value: '1' }, { label: 'Option 2', value: '2' }, { label: 'Option 3', value: '3' }, ]; return <> <Select value={val} options={options} onChange={(x) => console.log(x)} /> {/* However, this works! */} <Select value={val} options={options} onChange={setVal} /> </>; }
y coloque el cursor sobre la propiedad onChange, dice claramente que la propiedad de la devolución de llamada onChange es de tipo cadena. Si cambio el código y hago que el value
sea una matriz, el valor onChange también es de tipo matriz. Pero por alguna razón, el tipo no se infiere en la función que se pasa a la devolución de llamada y TypeScript se queja de que Parameter 'x' implicitly has an 'any' type.
Entonces mi pregunta: ¿Por qué TypeScript no puede inferir el tipo aquí, a pesar de que la función está escrita correctamente y puede inferir el tipo incluso para tipos de cadena personalizados?
Podría estar relacionado con mi configuración de tsconfig, así que lo agregué en el stackblitz de reproducción:
https://stackblitz.com/edit/react-ts-wuj3yu?file=Select.tsx,tsconfig.json
Eche un vistazo: https://stackblitz.com/edit/react-ts-agwgkq?file=Select.tsx,App.tsx
Tengo que negarme a definir tantos tipos/accesorios:
import React, { useState } from 'react'; export type SelectValue = string | number | Array<string> | Array<number>; type Unwrap<T> = T extends Array<infer R> ? R : T; export interface SelectOption<T extends SelectValue> { label: string; value: Unwrap<T>; } interface SelectProps<T extends SelectValue> { options: SelectOption<T>[]; value: T; onChange: (value: T) => void; placeholder?: string; disabled?: boolean; className?: string; searchable?: boolean; } export function Select<T extends SelectValue>(props: SelectProps<T>) { return <div>Dummy Select</div>; } ... import * as React from 'react'; import { Select } from './Select'; import './style.css'; export default function App() { const [val, setVal] = React.useState<string>(''); const options = [ { label: 'Option 1', value: '1' }, { label: 'Option 2', value: '2' }, { label: 'Option 3', value: '3' }, ]; const [multipleVal, setMultipleVal] = React.useState<Array<string>>([]); return ( <React.Fragment> <Select value={val} options={options} onChange={(x) => console.log(x)} /> // x is string {/* However, this works! */} <Select value={multipleVal} options={options} onChange={(x) => console.log(x)} // x is Array<string> /> </React.Fragment> ); }