Búsqueda de sitios web

¿Por qué TypeScript no puede inferir el tipo de retorno de esta función?


Estoy intentando escribir algunas funciones utilitarias que clonen objetos, pero cambien propiedades profundas.

Aquí hay un ejemplo de una versión algo inútil que simplemente reemplaza una propiedad nombrada de un objeto con algún otro valor que puede ser de un tipo diferente. El código funciona: reemplaza la propiedad nombrada con el nuevo valor, pero por alguna razón TypeScript no puede determinar el tipo del objeto creado y da un error de compilación. ¿Por qué no le gusta esto a TypeScript y cómo puedo hacerlo funcionar?

Podría definir explícitamente el tipo de retorno, pero esto se vuelve complicado con las versiones de esta función que cambian una propiedad varios niveles más abajo.

it('change type of input object', () => {
  const cloneWith = <T extends Record<string, unknown>, A extends keyof T, V>(
    i: T,
    a: A,
    value: V
  ) => ({
    ...i,
    [a]: value,
  });
  const source = { name: 'bob', age: 90 };
  const result = cloneWith(source, 'age', 'old!');

  // This test passes
  expect(result.age).to.equal('old!');

  // But compile error - number and string have no overlap -
  // on this line
  if (result.age === 'old!') {
    console.log('That person is old!');
  }
});

Respuesta #1

Los tipos inferidos para los literales de objeto creados con la sintaxis de propagación no siempre son ideales, especialmente cuando se involucran propiedades computadas. En este caso, T se intersecta con una firma de índice {[x: string]: V}, mientras que Omit<T, A> & {[K in A]: V} tendría más sentido (la intersección de T sin la propiedad A y un objeto separado con clave A y valor T).

No veo cómo podrías resolver esto sin un tipo de retorno explícito, pero si agregas el tipo de retorno, al menos se infieren los tipos correctos. Quizás puedas hacer algo similar para las otras versiones de tu función.

const cloneWith = <T, A extends keyof T, V>(
  i: T,
  a: A,
  value: V
): Omit<T, A> & {[K in A]: V} => ({
  ...i,
  [a]: value,
});

const source = { name: 'bob', age: 90 };
const result = cloneWith(source, 'age', 'old!');
type TestSource = typeof source.age // number
type TestResult = typeof result.age // string

Área de juego de TypeScript

Artículos relacionados