typescript - Mantener el tipo de const en variables de solo lectura asignadas a tipos
Me gustaría escribir un arreglo de manera que los únicos valores permitidos en el arreglo sean los valores de una cierta clave en una lista de objetos.
En principio, sé que esto se puede hacer así:
const page1 = {
videoTitles: ['someTitle', 'otherTitle'],
} as const
const page2 = {
videoTitles: ['title', 'goodTitle'],
} as const
const allVideos = [...page1.videoTitles, ...page2.videoTitles]
// As desired, the if statement raises an error because 'badTitle' isn't in either of the lists
if (allVideos.includes('badTitle')) {
console.log('Found!')
}
Sin embargo, lo que me gustaría hacer es dar un tipo a los objetos y obtener el mismo comportamiento, pero simplemente no puedo averiguar cómo hacerlo. Esto es más o menos lo que me gustaría:
type PageWithVideos = { readonly videoTitles: readonly string[] }
const page1: PageWithVideos = {
videoTitles: ['someTitle', 'otherTitle'],
} as const
const page2: PageWithVideos = {
videoTitles: ['title', 'goodTitle'],
} as const
const allTitles = [...page1.videoTitles, ...page2.videoTitles]
// Now, however, I don't get the same error. allTitles is just a string[]
if (allTitles .includes('badTitle')) {
console.log('Found!')
}
He descubierto que realmente puedo forzarlo con algo así:
type PageWithVideos<T extends string> = { readonly videoTitles: readonly T[] }
const page1: PageWithVideos<'someTitle' | 'otherTitle'> = {
videoTitles: ['someTitle', 'otherTitle'],
} as const
const page2: PageWithVideos<'title' | 'goodTitle'> = {
videoTitles: ['title', 'goodTitle'],
} as const
const allVideos = [...page1.videoTitles, ...page2.videoTitles] as const
// I'm getting the error again (which is good), but this is a little silly
if (allVideos.includes('badTitle')) {
console.log('Found!')
}
Pero preferiría no tener que duplicar cada título en la lista y en el genérico. ¡Cualquier opinión sería apreciada!
Editar - Caso de uso:
Construyendo un sitio de portafolio solo de FE para un documentalista. Los videos están alojados en plataformas de video externas (YouTube, Vimeo) y se están insertando en el sitio.
El cliente tiene, digamos, 100 videos. Hay aproximadamente 7 vistas diferentes para los videos. Al cliente le gustaría poder, en cada pantalla, seleccionar qué videos se muestran y en qué orden. Muchos videos aparecerían en varias pantallas.
Me gustaría ofrecer una solución en la que el cliente pueda añadir objetos Video a algún archivo videos.ts. Al agregar los videos, los valores se verifican mediante comprobación de tipos. Luego quiero que el cliente vaya a otro archivo (recentWork.ts) y agregue los títulos de los videos que le gustaría mostrar en esa página. Pero lo que realmente quiero es que la lista de títulos en recentWork.ts se verifique mediante comprobación de tipos con respecto a la lista de todos los nombres de video en videos.ts.
// videos.ts
export const videos: Video[] = [{
title: 'someTitle',
...
}, {
title: 'title',
...
}]
// recentWorks.ts
export const recentWorkPageInfo = {
videoTitles: ['title', 'someTitle'], // type-checked against `title` key from videos
...
}
Solución: ¡@ghybs lo consiguió!
// videos.ts
export const videos = [{
title: 'someTitle',
...
}, {
title: 'title',
...
}] as const satisfies Video[]
// recentWorks.ts
import { videos } from '../videos'
type PageInfo = {
videoTitles: (typeof videos)[number]['title'][]
}
export const recentWorkPageInfo = {
videoTitles: ['title', 'someTitle'], // type-checked!!
...
}
Respuesta #1
Esto suena como el caso de uso para el operador satisfies (introducido en TypeScript 4.9):
El nuevo operador
satisfiesnos permite validar que el tipo de una expresión coincida con algún tipo, sin cambiar el tipo resultante de esa expresión.
Así que puedes capturar objetos que no cumplen con la forma de PageWithVideos:
const page3 = {
videoTitles: [0], // Error: Type 'number' is not assignable to type 'string'.
// ~
} as const satisfies PageWithVideos
const page4 = {
titles: ["badKey"], // Error: Object literal may only specify known properties, and 'titles' does not exist in type 'PageWithVideos'.
// ~~~~~~
} as const satisfies PageWithVideos
...pero su tipo todavía se infiere correctamente (en particular, su tipo literal a partir de su contenido, gracias a la afirmación as const), y mantienes el comportamiento deseado:
const page1 = {
videoTitles: ['someTitle', 'otherTitle'],
} as const satisfies PageWithVideos
const page2 = {
videoTitles: ['title', 'goodTitle'],
} as const satisfies PageWithVideos
const allTitles = [...page1.videoTitles, ...page2.videoTitles]
if (allTitles.includes('badTitle')) { // Error: Argument of type '"badTitle"' is not assignable to parameter of type '"someTitle" | "otherTitle" | "title" | "goodTitle"'.
// ~~~~~~~~~~
console.log('Found!')
}
Enlace del parque
