¿Cómo hacer de esto un tipo expandible en TypeScript?
Tengo un tipo MeasurementData que se ve así.
type MeasurementData = {
measurements: {
x: number | Date;
y: number;
}[];
xUnits: string;
xFirstValue: number;
xLastValue: number;
xSecondToLastValue: number;
xAverage: number;
xMin: number;
xMax: number;
xDelta: number;
yUnits: string;
yFirstValue: number;
yLastValue: number;
ySecondToLastValue: number;
yAverage: number;
yMin: number;
yMax: number;
yDelta: number;
};
Pero ahora la sección measurements se va a expandir con un posible número desconocido de valores y.
por ejemplo.
measurements: {
x: number | Date;
y0: number;
y1: number;
y2: number;
y3: number;
y4: number;
y5: number;
}[];
Siendo nuevo en TypeScript, no estoy seguro de cómo ampliar MeasurementData para manejar este escenario.
¿Cómo haces para no repetir todas las definiciones de cálculos y?
por ejemplo - No quiero hacer esto.
y0Units: string;
y0FirstValue: number;
y0LastValue: number;
y0SecondToLastValue: number;
y0Average: number;
y0Min: number;
y0Max: number;
y0Delta: number;
y1Units: string;
y1FirstValue: number;
y1LastValue: number;
y1SecondToLastValue: number;
y1Average: number;
y1Min: number;
y1Max: number;
y1Delta: number;
y2Units: string;
y2FirstValue: number;
y2LastValue: number;
y2SecondToLastValue: number;
y2Average: number;
y2Min: number;
y2Max: number;
y2Delta: number;
/// y3, y4, y5 ...etc...
Respuesta #1
Eh, este es un tipo de pesadilla. Es casi posible, pero te imploro que refactorices tu tipo de datos para usar arreglos en lugar de una lista cada vez más larga de claves de propiedad con números dentro del nombre de la clave. Pero me gusta hacer que TypeScript haga cosas terribles, así que aquí vamos:
Quieres que MeasurementData sea la unión infinita de todas las versiones de la estructura para los diferentes números de propiedades y. Como MeasurementDataElement<0> | MeasurementDataElement<0 | 1> | MeasurementDataElement<0 | 1 | 2> | MeasurementDataElement<0 | 1 | 2 | 3> | ....
Realmente no puedes hacer uniones infinitas. Puedes hacerlo como una restricción genérica, o como una unión de hasta un número finito. Voy a hacer lo segundo y escoger, digamos, 40, para que puedas soportar y0 hasta y39.
Debido a que esto implica concatenación de cadenas y recursión, usaré la versión beta de TypeScript 4.1. TypeScript 4.1 soportará tipos de literales de plantilla y la ampliación de claves de tipos mapeados, así como tipos condicionales recursivos:
type CommonSuffixProps = {
Units: string;
FirstValue: number;
LastValue: number;
SecondToLastValue: number;
Min: number;
Max: number;
Delta: number;
}
type CommonPrefixes<N extends number> = 'x' | `y${N}`
type MeasurementDataElement<N extends number> = Pick<
{ measurements: { [K in CommonPrefixes<N>]: ('x' extends K ? Date : never) | number }[]; } &
{ [K in keyof CommonSuffixProps as `${CommonPrefixes<N>}${K}`]: CommonSuffixProps[K] },
'measurements' | `x${keyof CommonSuffixProps}` | (N extends any ? `y${N}${keyof CommonSuffixProps}` : never)
> extends infer O ? { [K in keyof O]: O[K] } : never;
type TuplesOfUnionsOfNumbers<N extends number, A extends number[] = []> =
N extends A['length'] ? A : TuplesOfUnionsOfNumbers<N, [...A, A[number] | A['length']]>;
type MeasurementDataForTuplesOfUnionsOfNumbers<VN extends { [k: number]: number }> = {
[K in keyof VN]: MeasurementDataElement<Extract<VN[K], number>>
}[number]
// only works up to maximum number of 40 (y0, y1, y2, y3, y4, ... y38, y39)
type MeasurementData = MeasurementDataForTuplesOfUnionsOfNumbers<TuplesOfUnionsOfNumbers<40>>
Eso es UN GRAN DESORDEN 🌪 y no estoy seguro de que valga la pena explicarlo en detalle. Daré los aspectos más destacados:
Probablemente puedes ver que
CommonSuffixPropsse parece a las cosas que quieres añadir al final dexyy0yy1, etc.El tipo
CommonPrefixes<N>convierte0 | 1 | 2en'x' | 'y0' | 'y1' | 'y2'El tipo
MeasurementDataElement<N>convierte0 | 1en el tipo que estás buscando, donde su propiedadmeasurementses{x: number | Date, y0: number, y1: number}[]y donde sus otras propiedades se parecen a combinar elCommonPrefixes<0 | 1>con elCommonSuffixProps.El tipo
TuplesOfUnionsOfNumbers<N>convierte4en la tupla[0, 0|1, 0|1|2, 0|1|2|3]. Estoy usando esto para que podamos convertir un solo número como40en la gran lista de posibles conjuntos de propiedadesyque necesitamos.El
MeasurementDataForTuplesOfUnionsOfNumbers<T>toma uno de esos tuplas-de-uniones-de-números de antes y te da la unión deMeasurementDataElement<N>para todos losNque hay allí.Finalmente,
MeasurementDataes simplementeMeasurementDataForTuplesOfUnionsOfNumbers<TuplesOfUnionsOfNumbers<40>>, o el tipo que estamos buscando.
Este tipo es tan molesto de inspeccionar con IntelliSense. Digamos que tomamos MeasurementData y miramos el único elemento de la unión con una propiedad y1Min pero sin una propiedad y2Min:
type Example = Extract<Exclude<MeasurementData, {y2Min: any}>, {y1Min: any}>;
/* type Example = {
measurements: {
x: number | Date;
y0: number;
y1: number;
}[];
xUnits: string;
xFirstValue: number;
xLastValue: number;
xSecondToLastValue: number;
xMin: number;
xMax: number;
xDelta: number;
y0Units: string;
y0FirstValue: number;
y0LastValue: number;
y0SecondToLastValue: number;
... 9 more ...;
y1Delta: number;
} */
Eso parece correcto, supongo. Como evidencia adicional, intentemos hacer algunas pruebas sobre un valor de tipo MeasurementData:
declare const md: MeasurementData;
if ("y23Min" in md) {
md.y14SecondToLastValue.toFixed(2);
for (let m of md.measurements) {
m.y19.toFixed(2);
}
}
Al probar si y23Min está en el valor, el compilador sabe que es uno de los elementos de la unión con al menos 24 coordenadas y, por lo que te permitirá indexar, por ejemplo, en y14SecondToLastValue o measurements[0].y19.
¡Hurra! 😩
Ahí lo tienes. De nuevo, te ruego que no uses este tipo. En su lugar, te sugiero encarecidamente que rehagas tu código en una estructura de datos más amigable con TypeScript. ¿Tal vez algo así?
interface Friendly<Y extends CommonSuffixProps[]> {
measurements: { x: number, y: { [K in keyof Y]: number } }[],
axisProperties: { x: CommonSuffixProps, y: Y }
}
function acceptFriendly<Y extends CommonSuffixProps[]>(f: Friendly<[...Y]>) { }
declare const axisProps: CommonSuffixProps;
acceptFriendly({
measurements: [
{ x: 0, y: [1, 2, 3] },
{ x: 1, y: [2, 3, 4] }
],
axisProperties: {
x: axisProps,
y: [axisProps, axisProps, axisProps]
}
})
Es una restricción genérica para el número de propiedades y, y por lo demás estamos cambiando y3Min a algo como axisProperties.y[3].Min. Esto también se puede iterar más fácilmente, ¿verdad? Pero, en última instancia, depende de ti si quieres refactorizar. Incluso si no quieres, realmente no puedo recomendar el monstruo de Frankenstein de un tipo mencionado arriba. Por favor, considera esto como una lección de precaución en lugar de una solución.
Enlace de Playground al código
