Home > Blockchain >  Typescript array of different generics
Typescript array of different generics

Time:10-21

Using TypeScript, I want to create an Array of different generics.

My goal of my generic class is to represent table columns, and the T holds the type of the value the column will hold. This looks as follows:

export class TableColumnConfig<T> { ... }

When I want to put multiple columns for a table into the array, I do this:

const columns: TableColumnConfig<any>[] = [];

Then I would do something like this:

columns.push(new TableColumnConfig<number>());
columns.push(new TableColumnConfig<string>());

This works, but the thing is that eslint tells me that I should not use any

Unexpected any. Specify a different type.eslint@typescript-eslint/no-explicit-any

Eslint suggest to use unknown.

So I do this:

const columns: TableColumnConfig<unknown>[] = [];

So of course, I get the following error:

Argument of type 'TableColumnConfig<number>' is not assignable to parameter of type 'TableColumnConfig<unknown>'. Type 'unknown' is not assignable to type 'number'.ts(2345)

Is there any way of satisfying eslint as well as getting no typescript syntax error?

Is it fine in this case to ignore the warning of eslint?

Some advice is much appreciated! :)

EDIT: This is how my class and the builder that I use for it looks like:

export class TableColumnConfig<T> {

    // lots of properties (that don't use T)

    format?: (val: T) => string;
    sort?: (val1: T, val2: T) => number;

    constructor() {
        // ...
    }
}

export class TableColumnConfigBuilder<T> implements ITableColumnConfigBuilder<T> {
  private tableColumnConfig: TableColumnConfig<T>;

  constructor() /* some mandatory properties */
  {
    this.tableColumnConfig = new TableColumnConfig(
      sourceAddress,
      parameterAddress,
      dataType,
      mainLabel
    );
  }

  // ...

  setFormat(format: (val: T) => string): this {
    this.tableColumnConfig.format = format;
    return this;
  }
  setSort(sort: (val1: T, val2: T) => number): this {
    this.tableColumnConfig.sort = sort;
    return this;
  }

  get(): TableColumnConfig<T> {
    return this.tableColumnConfig;
  }
}


interface ITableColumnConfigBuilder<T> {
    // ...
  setFormat(format: (val: T) => string): this;
  setSort(sort: (val1: T, val2: T) => number): this;
}

CodePudding user response:

The error you are seeing here is related to variance. The two functions format and sort use T in a contravariant position making the type TableColumnConfig contravariant which reverses assignability. You may also have other properties in TableColumnConfig in a covariant position making the type ultimately invariant.

You can bypass the error by using methods instead of properties containing functions. As I learned today, methods can be optional too.

export class TableColumnConfig<T> {

    format?(val: T): string
    sort?(val1: T, val2: T): number

    constructor() {}
}

const columns: TableColumnConfig<unknown>[] = [];

// this works fine now
columns.push(new TableColumnConfig<number>());
columns.push(new TableColumnConfig<string>());

This works because variance works differently for methods. See here.

Method types both co-vary and contra-vary with their parameter types


Playground

CodePudding user response:

You can make the type of columns precisely reflect the types of the columns it contains:

const columns: (TableColumnConfig<number> | TableColumnConfig<string>)[] = [];
// −−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Playground link

Note that TypeScript will do that for you if you define columns with a literal and don't provide an explicit type:

const columns = [
    new TableColumnConfig<number>(),
    new TableColumnConfig<string>(),
];

Playground link

Of course, that's not always possible.


But if you don't want to be precise about the type, then any is where you'd go.

  • Related