typescript - Tipos de objeto condicionales cuando el valor cambia
Perdón por el título de la pregunta, no estoy muy seguro de cómo formularlo.
Soy bastante nuevo en Typescript y quería agregar tipos fuertes a un nuevo proyecto.
Digamos que tengo una función que acepta dos parámetros: value y params escrita así
type Value = 'category' | 'follows' | 'users';
request(value: Value, params: object)
Como puedes ver, el tipo Value solo acepta tres valores predefinidos. Pero params debería tiparse condicionalmente cuando value cambie:
Si el valor es category, entonces los parámetros deberían tener tipo { author: string, year: number }, si es users entonces debería tener tipo { id: number } O { name: string }
Y ese es todo el desafío de esto. Puedo hacerlo con objetos e índices, pero me he quedado atascado con los tipos.
¿Es siquiera posible hacer esto?
¡Muchas gracias!
NVH
EDITAR: Muchas gracias por toda su ayuda, también disfruté mucho leyendo toda la documentación, ya que quiero tener los tipos más seguros para mi función, optaré por la solución de @captain-yossarian, ¡pero las otras también funcionaron!
Respuesta #1
Funciona solo con TS nightly, ve este PR.
type Value = 'category' | 'follows' | 'users';
type Arguments =
| ['category', { author: string, year: number },]
| ['users', { id: number } | { name: string }]
| ['follows', Record<PropertyKey, unknown>]
const request = (...[value, params]: Arguments) => {
if (value === 'category') {
params.author // ok
params.year
}
return 'UNIMPLEMENTED'
}
Parque infantil
Consulta rest-parameters-with-tuple-types para más explicación sobre el uso de la sintaxis spread en ts
Sin embargo, podrías tener problemas con users. Ve el ejemplo:
const request = (...[value, params]: Arguments) => {
if (value === 'category') {
params.author // ok
params.year
}
if(value==='users'){
params.id // <------------- error
}
return 'UNIMPLEMENTED'
}
TypeScript no te permite obtener params.id. De hecho, TS no te permite obtener ninguna propiedad de params si value es users porque la unión { id: number } | { name: string } no tiene propiedades en común. Por lo tanto, la forma más segura es prohibir el uso de cualquier propiedad.
Para reducir el tipo de params cuando value es users, creo que vale la pena usar un guardia de tipo personalizado:
type Value = 'category' | 'follows' | 'users';
type UsersId = { id: number }
type UsersName = { name: string }
// TYPE GUARD
const isUserId = (data: UsersId | UsersName): data is UsersId => 'id' in data
// TYPE GUARD
const isUserName = (data: UsersId | UsersName): data is UsersName => 'name' in data
type Arguments =
| ['category', { author: string, year: number },]
| ['users', UsersId | UsersName]
| ['follows', Record<PropertyKey, unknown>]
const request = (...[value, params]: Arguments) => {
if (value === 'category') {
params.author // ok
params.year
}
if (value === 'users') {
if(isUserId(params)){
params.id // ok
}
}
return 'UNIMPLEMENTED'
}
Respuesta #2
Hay muchas maneras de hacer esto, pero aquí hay dos.
Sobrecargas de funciones (documentación)
function request(value: 'category', params: { author: string, year: number }): void
function request(value: 'users', params: { id: number } | { name: string }): void
function request(
value: 'category' | 'users',
params: { author: string, year: number } | { id: number } | { name: string }
) {
// TODO: implementation
}
Parque infantil
Tipo de búsqueda con una función genérica (documentación)
type Value = 'category' | 'follows' | 'users';
type ObjectLookup = {
category: { author: string, year: number }
users: { id: number } | { name: string }
follows: { foo: string }
}
function request<T extends Value>(value: T, params: ObjectLookup[T]) {
// TODO: implementation...
}
Parque infantil
