У меня сейчас есть этот метод
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
Однако это не работает правильно, так как я всегда получаю объединение всех типов и не могу использовать произвольные строки.
У вас есть другая идея, как я мог бы реализовать это?
Вы можете использовать интерфейс для сопоставления строкового типа с истинным типом. Поскольку интерфейсы являются открытыми, клиенты могут использовать расширение модуля для добавления дополнительных параметров:
// 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
@LppEdd вы хотите, чтобы расширение было доступно только в определенной области?
Это был бы огромный плюс, да!
@LppEdd добавил версию расширения с ограниченной областью действия.
Спасибо еще раз! Я думаю, что в версии объявления метод должен быть create<T extends string>
, без ElementType
, иначе он не скомпилируется, а в примере с областью видимости просто удалите export
из CustomElementMap
. Я пытаюсь понять часть infer E
хахаха
@LppEdd он должен скомпилироваться с этим ... не имеет большого значения, так как ElementType | string
будет просто string
. TMap extends Record<T, infer E>
просто сообщает компилятору, есть ли у TMap
ключ T
, вставленный E
, тип этого ключа :)
Хорошо, моя вина... Я просто использовал не тот Тип. Спасибо :)
Спасибо! Но разве это действует только в случае реэкспорта, верно? Что, если я хочу расширить его только в рамках обычного метода TS? (в основном в коде реализации)