Сложный общий метод TypeScript

У меня сейчас есть этот метод

create<T extends ElementType | string>(type: T): Element<T>;

который использует

export type ElementType = 'ExtensionElements' | 'Documentation';

export type Element<T> =
  T extends 'ExtensionElements' ? ExtensionElements :
    T extends 'Documentation' ? Documentation :
      GenericElement;

Этот метод находится в .d.ts и гарантирует, что результат всегда будет типизирован, так что

const e1 = obj.create('ExtensionElements');
      ^^ type is ExtensionElements

const e2 = obj.create('Documentation');
      ^^ type is Documentation

const e3 = obj.create('Other');
      ^^ type is GenericElement

Теперь я хотел бы предоставить пользователям этого метода продлевать возможные типизированные варианты, чтобы, например,

type CustomElementType = 'Other' | ElementType;

type CustomElement<T> =
  T extends 'Other' ? CustomOtherElement : Element<T>;

const e4 = obj.create<CustomElementType, CustomElement>('Other');
      ^^ type is CustomOtherElement

Однако это не работает правильно, так как я всегда получаю объединение всех типов и не могу использовать произвольные строки.

У вас есть другая идея, как я мог бы реализовать это?

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
0
410
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать интерфейс для сопоставления строкового типа с истинным типом. Поскольку интерфейсы являются открытыми, клиенты могут использовать расширение модуля для добавления дополнительных параметров:

// create.ts
export declare let obj: {   
    create<T extends ElementType | string>(type: T): Element<T>;
}
type ExtensionElements = { e: string }
type Documentation = { d: string }
type GenericElement = { g: string }

export type ElementType = 'ExtensionElements' | 'Documentation';
export interface ElementMap {
    'ExtensionElements': ExtensionElements;
    'Documentation': Documentation;
}
export type Element<T extends string> = ElementMap extends Record<T, infer E> ? E :
    GenericElement;

const e1 = obj.create('ExtensionElements'); // ExtensionElements
const e2 = obj.create('Documentation'); // Documentation
const e3 = obj.create('Else'); //GenericElement

// create-usage.ts
import { obj } from './create'
type CustomOtherElement = { x: string }

declare module './create' {
    export interface ElementMap {
        'Other': CustomOtherElement
    }
}
const e4 = obj.create('Other'); //  CustomOtherElement

Если вы хотите сделать расширение с ограниченной областью действия, вам понадобится дополнительная функция, которая изменит интерфейс, используемый для сопоставления строки с типом объекта. Этот метод может просто вернуть текущее приведение объекта в качестве ожидаемого результата (поскольку типы не имеют значения во время выполнения, ничто не должно отличаться)

// create.ts
interface Creator<TMap = ElementMap>{
    create<T extends keyof TMap | string>(type: T): Element<TMap, T>;
    extend<TMapExt extends TMap>(): Creator<TMapExt>
}
export declare let obj: Creator
type ExtensionElements = { e: string }
type Documentation = { d: string }
type GenericElement = { g: string }

import { obj, ElementMap } from './create'
type CustomOtherElement = { x: string }

export type ElementType = 'ExtensionElements' | 'Documentation';
export interface ElementMap {
    'ExtensionElements': ExtensionElements;
    'Documentation': Documentation;
}
export type Element<TMap, T extends PropertyKey> = TMap extends Record<T, infer E> ? E : GenericElement;

const e1 = obj.create('ExtensionElements'); // ExtensionElements
const e2 = obj.create('Documentation'); // Documentation
const e3 = obj.create('Else'); //GenericElement


// create-usage.ts
export interface CustomElementMap extends ElementMap {
    'Other': CustomOtherElement
}
const customObj = obj.extend<CustomElementMap>()
const e4 = customObj.create('Other'); //  CustomOtherElement

Спасибо! Но разве это действует только в случае реэкспорта, верно? Что, если я хочу расширить его только в рамках обычного метода TS? (в основном в коде реализации)

LppEdd 30.05.2019 13:25

@LppEdd вы хотите, чтобы расширение было доступно только в определенной области?

Titian Cernicova-Dragomir 30.05.2019 13:26

Это был бы огромный плюс, да!

LppEdd 30.05.2019 13:28

@LppEdd добавил версию расширения с ограниченной областью действия.

Titian Cernicova-Dragomir 30.05.2019 14:07

Спасибо еще раз! Я думаю, что в версии объявления метод должен быть create<T extends string>, без ElementType, иначе он не скомпилируется, а в примере с областью видимости просто удалите export из CustomElementMap. Я пытаюсь понять часть infer E хахаха

LppEdd 30.05.2019 14:21

@LppEdd он должен скомпилироваться с этим ... не имеет большого значения, так как ElementType | string будет просто string. TMap extends Record<T, infer E>просто сообщает компилятору, есть ли у TMap ключ T, вставленный E, тип этого ключа :)

Titian Cernicova-Dragomir 30.05.2019 14:26

Хорошо, моя вина... Я просто использовал не тот Тип. Спасибо :)

LppEdd 30.05.2019 14:30

Другие вопросы по теме

Похожие вопросы