Class that extends Array and properly overrides 'map' method - javascript

I try to implement a class that extends Array and properly overrides 'map' method. the items in the array must extend a certain type.
implementation without a constrain on the items is working as expected:
export class MyClass<T> extends Array<T> {
constructor(...items: T[]) {
super(...items);
// Set the prototype explicitly. (typescript does not allow to extend base classes like Array)
// see: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work
Object.setPrototypeOf(this, VectorArr.prototype);
}
map<K>(callback: (value: T, index: number, array: T[]) => K, thisArg?: any): MyClass<K> {
const mapped = super.map(callback, thisArg);
return new MyClass(...mapped);
}
}
now lets say each item in the array must be extend Vector type:
interface MyVector {}
export class MyClass<T extends MyVector> extends Array<T> {
constructor(...items: T[]) {
super(...items);
// Set the prototype explicitly. (typescript does not allow to extend base classes like Array)
// see: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work
Object.setPrototypeOf(this, VectorArr.prototype);
}
// TS2416: Property 'map' in type 'MyClass<T>' is not assignable to the same property in base type 'T[]'.   Types of parameters 'callback' and 'callbackfn' are incompatible.     Type 'U' is not assignable to type 'MyVector'. lib.es5.d.ts(1441, 9): This type parameter might need an `extends MyVector` constraint.
// v
map<K extends MyVector>(callback: (value: T, index: number, array: T[]) => K, thisArg?: any): MyClass<K> {
const mapped = super.map(callback, thisArg);
return new MyClass(...mapped);
}
}
now we got an error: basically, typescript says that because the base class(the native Array) implementation does not have to extend MyVector(Da!) my implementation is wrong(not compatible with the base class method - generic type K from my implementation is extending MyVector but generic type U from the native implementation does not have this constrain).
well, seriously? I try to enforce a more specific type than the native array, why does typescript suggest me to extend the base class method implementation so it will extend MyVector as well?
how do I make typescript stop yelling it me?

Related

T extends String as index of mapped type

In my Project, I am using a mapped type (with strings as keys):
export type ConfigurableFieldsType<T extends string[]> = {
[field in keyof T]: string
}
In the following method, I am using the type (this.configurableFields is a ConfigurableFieldsType object):
private getConfigurableAttribute(attribute: C[number]): string {
return this.configurableFields[attribute]
}
However, I am getting the error:
Type 'C[number]' cannot be used to index type 'ConfigurableFieldsType<C>'.
You can find the complete example (with reproducible error) in this TS Playground or in the following:
export type ProductType<T extends string[]> = {
// more variables
configurableFields: ConfigurableFieldsType<T>
}
export type ConfigurableFieldsType<T extends string[]> = {
[field in keyof T]: string
}
export class Product<C extends string[]> implements ProductType<C> {
constructor(
// more public parameters
public configurableFields: ConfigurableFieldsType<C>,
) {}
private getConfigurableAttribute(attribute: C[number]): string {
return this.configurableFields[attribute]
}
}
You intend ConfigurableFieldsType<["a", "b", c"]> to evaluate to something like {a: string, b: string, c: string}, but your current definition would result in [string, string, string]. That's because {[K in keyof T]: string} results in a mapped array type; T is an arraylike type, and keyof T is thus the array keys, like "push" or "length".
Instead, you want the keys of your type to be elements of T. That is, the types you get when you index into T with a numeric index... so instead of keyof T, you want T[number]:
export type ConfigurableFieldsType<T extends string[]> = {
[K in T[number]]: string
}
type Example = ConfigurableFieldsType<["a", "b", "c"]>
/* type Example = {
a: string;
b: string;
c: string;
} */
And now things work as you intend:
private getConfigurableAttribute(attribute: C[number]): string {
return this.configurableFields[attribute] // okay
}
Playground link to code

Why the genetric type can not be used as type for keys? [duplicate]

This question already has answers here:
Combining generics with index type
(2 answers)
Closed 10 months ago.
type Test<T extends string, V extends unknown> = {
[Key: T]: V;
}
type Test<T extends string, V extends unknown> = {
[Key in T]: V;
}
From the code above, T extends string,why T can not be used as Key's type and it output an error An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead. But if replace Key: T with Key in T, it works.
I think what you're looking for in TypeScript is called Utility Type - Record
For example:
type Test<T extends string, V extends unknown> = Record<T, V>;
Docs: https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type

A mapped type may not declare properties or methods - TypeScript

I have an interface, which should have keys that are in specific enum key type, but when I declare the type it gives this error:
A mapped type may not declare properties or methods.
Here is the code:
enum myEnum {
propOne = 'propOne',
propTwo = 'propTwo'
}
export interface myInterface {
[key in myEnum]: anotherInterface;
}
I tried to specify the type also like this, but it didn't work and gave me syntax error:
export interface myInterface {
[key in keyof typeof myEnum]: anotherInterface;
}
I also tried to use a normal object instead of an enum, but it gave the same error.
You need to use Mapped type, not interface:
export type MyInterface = {
[key in myEnum]: AnotherInterface;
}

Using keyof with a generic function and a newable

I am trying to create a function that looks like this:
export function getModel<T extends object>(model: new () => T, properties: { [key: keyof T]: any }): T {
}
The issue that I am having is that key errors saying:
An index signature parameter type must be 'string' or 'number'.
Basically what this is supposed to do is take a class reference, and you should be able to pass a list of the properties that are in the class reference to the second parameter.
export class A {
public b: string;
public c: boolean;
}
getModel(A, { b: 'cat', c: false});
So, What I would like is for the keys to be a list of the properties in the class within the object. How can this be done?

TypeScript array of generic classes

I'm having trouble getting the TypeScript compiler to not give me grief. I have a controller which has an array of classes, NOT the instance of the class, but the class itself. All of these will extend off of the same base, UnitView. For some reason I get an error if my TitleView does not also accept generics, but I get no errors if it does.
I'm not understanding why TitleView would need to accept generics because it passes the Model type explicitly to the UnitView. Can anyone explain or see anything that I'm doing wrong?
Code:
class Model { }
class View<TModel extends Model> {
private model: TModel;
}
class UnitView<TModel extends Model> extends View<TModel> { }
class TitleView extends UnitView<Model> { }
class Controller {
private ViewClass: typeof UnitView;
private ViewClasses: typeof UnitView[] = [
TitleView
]
}
And here is a direct link to TypeScript playground if you want to test it there
The error has nothing to do with arrays. Here is a minimal reproduction of your bug:
class View<TModel> {
model: TModel;
}
class UnitView<TModel> extends View<TModel> { }
class TitleView extends UnitView<{}> { }
const example1: typeof UnitView = TitleView; // ERROR
TileView is not assignable to typeof UnitView. The key reason being that the type of T (a generic) are not compatible with {}.
This is similar to the further simplified example shown below:
class Foo<T> {
model: T
}
class Bar{
model: {}
}
const example2: typeof Foo = Bar; // ERROR
TLDR
Generics T and instances (even {}) are not compatible for assignment. This is by design.
More
The only way to stay compatible is the preserve the generic as well e.g.
class Foo<T> {
model: T
}
class Bar<T>{
model: T
}
const example3: typeof Foo = Bar; // Okay
To complement basarat's answer, here's a working declaration:
private ViewClasses: (new (...args: any[]) => UnitView<{}>)[] = [
TitleView
]

Categories

Resources