es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Tipado de parámetros genéricos de TypeScript para funciones currificadas.

Intentando escribir una función curry que tome primero un functor y luego un objeto.

type TestType = {
  x: number;
  y: number;
  hello: string;
};

type CordinateType = {
  data: TestType;
  id: string;
};

const transform = <t extends="" object,="" k="" extends="" keyof="" t="">(
  functor: (val: T[K]) => any
) => (obj: T) =>
  Object.entries(obj).reduce((acc, [k, v]) => ({ ...acc, [k]: functor(v) }), {});

const testData: CordinateType = {
  id: "uuid-222-uuid",
  data: {
    x: -3,
    y: 2,
    hello: "test"
  }
};

const positive = (x) => (Number.isInteger(x) ? Math.abs(x) : x);

const result = { ...testData, data: transform(positive)(testData) };
console.log("result", result);

transform es una función simple que toma un functor y mapea sobre un objeto.

El problema principal es que el tipo de result.data es {}, en lugar de TestType.

Este es un ejemplo de una aplicación de React, la función setState no puede deducir correctamente el tipo.

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Escribir una función curried con un functor y un objeto

    Problema

    Tenemos una función curried llamada transform que toma primero un functor y luego un objeto. El objetivo es mapear sobre el objeto y aplicar el functor a cada una de sus propiedades. Sin embargo, el tipo resultante del objeto mapeado es {} en lugar del tipo deseado TestType.

    Solución

    Para asegurar que el tipo resultante del objeto mapeado sea TestType, necesitamos ajustar el tipo de la función transform. En lugar de devolver un objeto plano, podemos devolver un objeto de tipo Partial<t> y luego hacer un typecast a T al final.

    Aquí está la función transform actualizada:

    const transform = <t extends="" object,="" k="" extends="" keyof="" t="">(
      fntor: (val: T[K]) => any
    ) => (obj: T): T =>
      (Object.entries(obj) as [K, T[K]][]).reduce(
        (acc, [k, v]) => ({ ...acc, [k]: fntor(v) }),
        {}
      ) as T;
    

    El tipo de retorno de la función transform ahora es T.

    Aquí hay una versión modificada de nuestro código de ejemplo con la función transform actualizada:

    type TestType = {
      x: number;
      y: number;
      hello: string;
    };
    
    type CordinateType = {
      data: TestType;
      id: string;
    };
    
    const positive = (x: number) => (Number.isInteger(x) ? Math.abs(x) : x);
    
    const transform = <t extends="" object,="" k="" extends="" keyof="" t="">(
      fntor: (val: T[K]) => any
    ) => (obj: T): T =>
      (Object.entries(obj) as [K, T[K]][]).reduce(
        (acc, [k, v]) => ({ ...acc, [k]: fntor(v) }),
        {}
      ) as T;
    
    const testData: CordinateType = {
      id: "uuid-222-uuid",
      data: {
        x: -3,
        y: 2,
        hello: "test",
      },
    };
    
    const result = { ...testData, data: transform(positive)(testData.data) };
    
    console.log("result", result);
    

    Toma en cuenta que pasamos testData.data a transform(positive) ya que solo estamos mapeando sobre la propiedad data del objeto testData.

    Tipificación del Hook useState en React

    En el segundo código sandbox, tenemos una aplicación de ejemplo de React con un hook useState. El problema es que la función setState del hook no puede deducir el tipo adecuado.

    Una solución es utilizar un argumento de tipo genérico para definir el tipo de nuestro estado. Aquí tienes un ejemplo:

    type StateType<t> = {
      loading: boolean;
      error: string | null;
      data: T | null;
    };
    
    type PostType = {
      userId: number;
      id: number;
      title: string;
      body: string;
    };
    
    const App = () => {
      const [state, setState] = useState<><posttype>>({
        loading: false,
        error: null,
        data: null,
      });
    
      const fetchData = async () => {
        setState((prev) => ({ ...prev, loading: true }));
    
        try {
          const response = await fetch(
            "https://jsonplaceholder.typicode.com/posts/1"
          );
    
          if (!response.ok) {
            throw new Error("Network response was not ok");
          }
    
          const data = await response.json();
          setState((prev) => ({ ...prev, data, error: null }));
        } catch (error) {
          setState((prev) => ({
            ...prev,
            error: `Error: ${error.message}`,
            data: null,
          }));
        } finally {
          setState((prev) => ({ ...prev, loading: false }));
        }
      };
    
      return (
        <div classname="App">
          <button onclick="{fetchData}">Fetch Data</button>
          {state.loading && Loading...</p>}
          {state.error && <p>{state.error}</p>}
          {state.data && (
            <div>
              <h1>{state.data.title}</h1>
              <p>{state.data.body}
            </div>
          )}
        </div>
      );
    };
    
    export default App;
    

    Observa que usamos StateType<posttype> como argumento de tipo genérico para definir el tipo de nuestro estado. Esto ayuda a TypeScript a deducir el tipo adecuado de nuestro estado cuando usamos la función setState.

    Conclusión

    En resumen, podemos tipificar una función curried con un functor y un objeto ajustando su tipo de retorno, y podemos tipificar el hook useState en React utilizando un argumento de tipo genérico para definir el tipo de nuestro estado. Con una tipificación adecuada, podemos hacer que nuestro código sea más robusto y fácil de mantener.

Comments are closed.