typescript - Arrays tipados en funciones genéricas
En una biblioteca tengo una función auxiliar que trabaja con arreglos de cualquier tipo. Ahora quiero extender eso para aceptar también arreglos tipados, pero no puedo encontrar una clase base común, ni los arreglos tipados son genéricos. Este es el código que tengo hasta ahora para arreglos genéricos y un arreglo tipado:
public static copyOf(original: Int32Array, newLength: number): Int32Array;
public static copyOf<T>(original: T[], newLength: number): T[];
public static copyOf<T>(original: T[] | Int32Array, newLength: number): T[] | Int32Array {
if (original instanceof Int32Array) {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Int32Array(newLength);
result.set(original);
return result;
} else {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Array<T>(newLength);
result.splice(0, 0, ...original);
return result;
}
}
Sin embargo, este enfoque requeriría añadir una rama propia para cada tipo de arreglo tipado, lo que iría en contra de la idea de los genéricos. ¿Cómo se podría implementar esto de una manera más compacta?
Respuesta #1
Primero para los typings, ya que esto es TypeScript. Actualmente no existe un tipo incorporado que corresponda a los arrays tipados; vea microsoft/TypeScript#15402. Pero nada le impide definir el suyo propio como una unión de los tipos individuales relevantes. Aquí hay una posibilidad:
type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array |
Uint32Array | Uint8ClampedArray | Float32Array | Float64Array;
No he incluido BigInt64Array o BigUint64Array allí, porque no tienen el mismo comportamiento exacto que los otros (necesitas bigint y no number), pero podrías añadirlos si los necesitas.
Ahora para la implementación. Una forma es usar la propiedad constructor del arreglo tipado que pases para obtener el constructor de clase correcto y usarlo para construir una nueva instancia. A TypeScript no le agradará que hagas esto, ya que no modela fuertemente las propiedades constructor tipadas (consulta ms/TS#3841 para una discusión extensa sobre esto)... así que necesitaremos usar una aserción de tipo para convencer al compilador de que es aceptable.
Posiblemente así:
class Foo {
public static copyOf<T extends TypedArray>(original: T, newLength: number): T;
public static copyOf<T>(original: T[], newLength: number): T[];
public static copyOf<T>(original: T[] | TypedArray, newLength: number): T[] | TypedArray {
if (!Array.isArray(original)) {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new (original.constructor as new (arg: number) => TypedArray)(newLength);
result.set(original);
return result;
} else {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Array<T>(newLength);
original.forEach((v, i) => result[i]=v); // splice doesn't do what you want
return result;
}
}
}
donde en lugar de verificar con instanceof para comprobar que el valor es un arreglo de tipo particular, verifico con el método Array.isArray() para comprobar que el valor no es un arreglo incorporado.
(También nota que tuve que cambiar del método splice() que inserta cosas, causando que el arreglo resultante sea más largo de lo que pretendes.)
Si necesitabas soportar BigInt64Array y BigUint64Array, entonces tendrías que añadir una afirmación de tipo en la línea result.set(original); para prevenir un error.
Asegurémonos de que funcione:
const arr = Foo.copyOf(new Int16Array([1, 2, 3, 4, 5]), 10);
// const arr: Int16Array
console.log(arr) // Int16Array: ...
const arr2 = Foo.copyOf(["a", "b", "c"], 4);
// const arr2: string[]
console.log(arr2) // ["a", "b", "c", ]
Se ve bien.
Enlace de Playground al código
