reactjs - Cómo obtener los tipos reales de los argumentos de una función pasada a otra función
Tengo un hook personalizado en React que realiza algunos efectos secundarios para hacer solicitudes fetch.
En su núcleo, el hook acepta una función de fetch y devuelve una versión decorada para ser llamada por el componente. Cuando se llama a la función decorada, establece la respuesta en el estado y también la devuelve (estructura de retorno similar a useState).
La función fetch pasada y la función decorada tendrán exactamente los mismos parámetros/tipos, pero el tipo de retorno será diferente.
El problema que tengo es al agregar tipos tanto a los datos de estado devueltos como a la función decorada devuelta. Parece que solo puedo hacer que funcione uno u otro sin obligar al usuario del hook a proporcionar lo que creo que es información duplicada.
// Dummy useState for dep-free example
function useState<State>(initial: State): [State, (newState: State) => void] {
return [initial, (newState: State) => {}];
}
function useFetch<Data, FetchParams extends any[]>(
fetchFunction: (...args: FetchParams) => Promise<Response>,
initial_value: Data | null = null
): [Data | null, (...args: FetchParams) => Promise<void>] {
const [response, setResponse] = useState<Data | null>(initial_value);
async function wrappedFetch(...args: FetchParams): Promise<void> {
// Side Effects
const res = await fetchFunction(...args);
const result = await res.json();
// Side Effects
if (res.ok) {
setResponse(result);
}
// Side Effects
}
return [
response,
wrappedFetch
];
}
En el uso, si proporcionas el tipo para el estado devuelto, también debes proporcionar los tipos de los parámetros, lo cual no me gusta. Usar Parameters de TypeScript hace que esto sea fácil, pero se siente innecesario. Este hook se utilizará cientos de veces en la base de código, y no quiero obligar a los desarrolladores a pasar lo que se siente como datos duplicados cada vez.
async function getStuff(value: string): Promise<Response> {
return new Promise((res) => {
res(new Response(value))
})
}
interface SampleData {
value: 'string';
}
const [test, getTest] = useFetch<SampleData, Parameters<typeof getStuff>>(getStuff)
getTest('test'); // Correctly typed, passing nothing or a non-string will error.
Si no pasas ningún parámetro de tipo a useFetch, inferirá el tipo correcto de la función, pero no tiene manera de conocer el tipo de test.
objetiva
Quiero poder usar el hook con el tipo correcto asociado tanto con test como con getTest, sin necesidad de proporcionar los argumentos, así:
const [test, getTest] = useFetch<SampleData>(getStuff)
// test = SampleData | null
// getTest = (value: string) => Promise<void>
Qué más he intentado
- Usando
Parametersdentro del hook:
async function wrappedFetch(...args: Parameters<typeof fetchFunction>): Promise<void> {
Pero esto parece usar el tipo predeterminado proporcionado (any[]) en lugar de basarse en la función real que se pasa.
- Proporcionando un valor predeterminado a
FetchParamspara que el desarrollador pueda pasar el tipoDatasin que se requieranFetchParams:
function useFetch<Data, FetchParams extends any[] = any[]>(
Pero proporcionar un valor por defecto evita que ocurra cualquier inferencia de tipo, haciendo que FetchParams sea inútil, y volvemos a obligar al desarrollador a pasar los argumentos cada vez, solo que sin seguridad de error/tipo si deciden no hacerlo.
Enlace al Playground de TypeScript