Parámetros predeterminados de Typescript para objetos pasados a una función
Me gustaría inicializar mi objeto pasado a la función de manera similar a como aparece en los valores escalares, por ejemplo:
someFunction(myValue: number = 5) { }
Entonces, mantengo tanto el tipo como el valor predeterminado. Me gustaría aplicar una construcción similar a lo siguiente:
someFunction(myObj: {val1: number, val2: number}) { }
Si voy directamente y simplemente hago la solución de copiar y pegar sin pensar:
someFunction(myObj: {val1: number = 5, val2: number = 10}) {}
Cada enfoque de inicialización da un error de TypeScript A type literal property cannot have an initializer.. Está bien, entendido, no puedo mezclar literales de tipo con inicialización. Usando una analogía tenemos:
someFunction(myValue: number = 5) { }
^^^^^^^ ^^^^^^ ^^
| | +-- Initialization value
| +-- Type literal
+-- Parameter name
Y análogamente:
someFunction(myObj: {val1: number, val2: number} = {val1: 5, val2: 10}) {}
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
| | +-- Initialization
| +-- Type literal
+-- Parameter name
La solución es usar una sintaxis mucho más verbosa:
someFunction(myObj: {val1: number, val2: number} = {val1: 5, val2: 10}) {}
La pregunta es si es posible, tener un pastel y comérselo: tener sintaxis concisa como en la definición de valor predeterminado de valor escalar (number=5) y control completo de tipos con la definición de objetos?
PD Solicito puntos extra por arte ASCII :)
Respuesta #1
Si aprovechas la desestructuración, puedes reducir mucho el código duplicado, sin embargo, es importante estar al tanto de las diferencias. Aquí está el patrón que generalmente uso.
function fun({ val1 = 1, val2 = 2 } = {}) {
return val1 + val2
}
La firma de esta función se infiere correctamente como function fun({ val1, val2 }?: { val1?: number; val2?: number; }): number.
Sin embargo, es importante tener en cuenta que esta función difiere en un par de formas importantes del original.
fun({ val1: 2 })resultará en queval2siga siendo el valor predeterminado,2Si necesitas un parámetro obligatorio (en el sentido de que si proporcionas algo, debes proporcionarlo), tienes que volver a especificar todo, lo que rápidamente se vuelve aún más desordenado ya que tienes inicializadores en dos ubicaciones. Esto se puede mitigar utilizando una interfaz para describir el tipo.
interface Args { val1?: number val2?: number val3: string } function fun({ val1 = 1, val2 = 2, val3 }: Args = { val3: ' ' }) { return val3.repeat(val1 + val2) }
Respuesta #2
No hay una forma integrada de hacer esto. La solución más simple sería dejar que el compilador infiera el tipo de parámetro basándose en el valor predeterminado:
function someFunction(myObj = { val1: 5, val2: 10 }) { }
El problema con esta solución es que todas las propiedades son obligatorias en el parámetro, por lo que tendrías que especificarlas incluso si su valor fuera indefinido.
Para evitar esto, podemos definir una función auxiliar que devuelva un tipo con todos los elementos marcados como opcionales:
function params<T>(o: T): Partial<T> {
return o;
}
function someFunction2(myObj = params({ val1: 5, val2: 10, otherValueWithoutDefault: null as number })) { }
O una versión que soporte tanto parámetros opcionales como obligatorios:
function params<TRequired>() : <TOptional>(o: TOptional) => Partial<TOptional> & TRequired
function params<TOptional>(o: TOptional) : Partial<TOptional>
function params<T>(o?: T): Partial<T> | (<TOptional>(o: TOptional) => Partial<TOptional> & T) {
if(o != null) {
return o;
} else {
return function(o : any) { return o; } as any
}
}
function someFunction2(myObj = params<{ required: boolean}>()({ val1: 5, val2: 10})) { }
someFunction2({
required: true,
val1: 0
})
