Typescript, не теряйте информацию о типе, используя обобщения

#typescript #typescript-typings #typescript-generics

#typescript #typescript-типизации #typescript-дженерики

Вопрос:

В приведенном ниже коде getValue метод в DataBankService классе теряет информацию о типе (см. Пример). Возможно ли сохранить информацию о типе?

https://stackblitz.com/edit/typescript-rx-playground-gqnzuq?file=index.ts

 import { Subject } from "rxjs/";

export interface DataObject<T> {
    value: T;
    observable: Subject<T>;
}

export class DataBankService {
    private dataBank: DataBankProperties;

    constructor() {
        this.dataBank = new DataBankProperties();
    }

    public getValue<T extends keyof DataBankProperties, V>(property: T) {
        return this.dataBank[property].value;
    }
}

 class DataBankProperties {

    money: DataObject<number> = this.createDataObject();
    token: DataObject<string> = this.createDataObject();
    testament: DataObject<object> = this.createDataObject();
 

    private createDataObject<T>() {
        return {
            value: null,
            observable: new Subject<T>(),
        };
    }
}

const dataBank = new DataBankService();

// token is now type >> token: string | number | object
const token = dataBank.getValue('token');
// I would like this to be type string

// token is now type >> token: string | number | object
const money = dataBank.getValue('money');
// I would like this to be type number
  

Ответ №1:

Вы можете сделать это с помощью утверждения для возвращаемого типа — используя свой generic в качестве ключа для DataBankProperties. Я переименовал его в K для соглашения (как в K для ключа)

 public getValue<K extends keyof DataBankProperties>(property: K): DataBankProperties[K]['value'] {
   return this.dataBank[property].value;
}
  

введите описание изображения здесь

введите описание изображения здесь

Комментарии:

1. Теперь тип — DataObject<число>/DataObject<строка> вместо number / строка

2. Вы использовали его как DataObject, а не как примитив

3. Большое вам спасибо, вы, кажется, довольно хороши в TS. Есть ли у вас какие-либо предложения по хорошим ресурсам TS?

4. Просто выберите тему и начните над ней работать! Чтобы узнать, как работает этот конкретный шаблон — я нашел эту документацию TS действительно полезной — если вы можете разобраться в том, как работает пример выбора, это в значительной степени то, что делается здесь: typescriptlang.org/docs/handbook /…

5. Как только вы освоитесь с этим, следующим уровнем, вероятно, будут условные типы — мне потребовалось много времени, чтобы понять, как их использовать, но один из хороших примеров приведен здесь. artsy.github.io/blog/2018/11/21/conditional-types-in-typescript . Наконец, пару лет назад я делал презентацию о расширенных TS — некоторые материалы здесь могут быть полезны для вас в качестве своего рода дорожной карты (игнорируйте контекст react, в первой половине презентации есть несколько примеров) github.com/m-b-davis/typescript-hocs-context/blob/master /…

Ответ №2:

Я бы предложил создать тип для вывода общего типа DataObject:

 type DataObjectTypeInference<T> = T extends DataObject<infer U> ? U : never;
  

А затем используйте это так:

 public getValue<K extends keyof DataBankProperties>(property: K) {
    return this.dataBank[property].value as DataObjectTypeInference<
        DataBankProperties[T]
    >;
}
  

Сначала вы определяете тип, который соответствует универсальному типу DataObject текущего экземпляра type. Я лично предпочитаю не связывать имена свойств и ставить лайк комментарию передо мной. Мне нравится, чтобы это выводилось с помощью TypeScript, поэтому, если вы решите изменить значение key на другое имя, это не повлияет на вывод типа.

После этого вы делаете утверждение типа переменной как DataBankProperties[T], которое дает вам тип DataObject, и теперь вы используете тип, который мы создали, и выводите универсальный тип DataObject.