TypeScript tipo genérico automático sin declarar explícitamente el tipo
Si tengo una interfaz con dos funciones. una función devuelve un valor. Quiero usar el tipo de valor en otra función en la misma interfaz. Usé genéricos pero tengo que declarar el tipo explícitamente cada vez que creo un nuevo objeto. pero podría hacerse sin ellos. Por ejemplo:
interface MyObject<T> {
create(): T
update(value: T): void // use value type defined in create method
}
y aquí definimos un nuevo objeto usando la interfaz anterior:
let test: MyObject<string> = {
create() {
return "Hello"
},
update(value) {
// do something with value
},
}
¿Es posible escribir MyObject sin <string>, ya que el tipo de valor ya fue definido en el método create?
Respuesta #1
Le gustaría que el compilador inferiera string para el parámetro de tipo genérico T. El único lugar donde ocurre tal inferencia de parámetro de tipo genérico es cuando llama a una función genérica, por lo que si desea este comportamiento, necesitará refactorizar de una anotación de tipo (const test: MyObject<string>=...) a llamar a una función auxiliar (const test=toMyObject(...)).
Actualización para TS4.7+:
Parece que microsoft/TypeScript#48358 se lanzará con TypeScript 4.7, momento en el cual el compilador podrá, de hecho, tipar contextualmente los parámetros de los métodos a partir de los parámetros de tipo inferidos de propiedades definidas previamente. En ese momento, lo siguiente funcionará:
const toMyObject = <T,>(o: MyObject<T>) => o;
let test = toMyObject({
create() {
return "Hello"
},
update(value) {
value.toUpperCase()
},
})
Tenga en cuenta que esto depende del orden de los miembros del literal de objeto, por lo que, dado que necesita que T se infiera a partir de create() antes de poder usarlo en update(), necesita que create() esté antes que update() en el objeto. Por lo tanto, lo siguiente falla:
let test2 = toMyObject({
update(value) {
value.toUpperCase() // error
},
create() {
return "Hello"
},
});
Enlace del playground para TS4.7+
Respuesta anterior para TS4.6-:
Además, deseas que el compilador infiera el tipo del parámetro de devolución de llamada value del método update. Este tipo de inferencia se llama tipado contextual.
Desafortunadamente, la inferencia de tipos contextual de los parámetros de devolución de llamada y la inferencia de tipos genéricos no funcionan bien juntas cuando ambos dependen del mismo objeto. Esta es una limitación de diseño de TypeScript. Vea microsoft/TypeScript#38872 para más información.
Para obtener tanto la inferencia de tipo contextual como la inferencia de parámetros de tipo genérico, tu función auxiliar necesitará dividir el objeto de entrada en dos partes: una para create de manera que se pueda inferir el parámetro de tipo genérico T, y la otra para update de manera que se pueda inferir el tipo del parámetro de la función de devolución de llamada value.
Eso nos da esto:
const toMyObject = <T,>(
create: () => T,
update: (value: T) => void
): MyObject<T> => ({ create, update });
Vamos a probarlo:
let test = toMyObject(
() => { return "Hello" },
(value) => { console.log(value.toUpperCase()); }
);
// let test: MyObject<string>
Se ve bien. El compilador infiere que test es del tipo MyObject<string> como se desea, y el parámetro de la función de devolución de llamada value también se infiere como string (como lo demuestra el hecho de que puedes llamar a value.toUpperCase() sin error en modo --strict).
Enlace de Playground al código para TS4.6-
