Пользовательское перечисление в общих типах машинописных текстов слишком сложно

если есть более простой способ записать это, так как это очень часто повторяется и кажется очень неправильным ...

const FolderVisibility = new Enum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}>({
    PUBLIC: 'public',
    PRIVATE: 'private'
}) as Enum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}> & {
    PUBLIC: 'public',
    PRIVATE: 'private'
}

Я хочу, чтобы среда IDE могла сказать мне, что FolderVisibility.PUBLIC == 'public' в качестве параметра в любом случае доступен только для чтения.

Вот класс Enum, у него несколько собственных свойств и одна функция

export default class Enum<T extends { [index: string]: string }> {
    private readonly map: T;
    public  readonly values: (T[keyof T])[];

    constructor(enums: T) {
        Object.defineProperty(this, 'map', { value: {} });

        for (let prop in enums) {
            if (enums.hasOwnProperty(prop)) {
                const value = enums[prop]
                if(typeof value != 'string'){
                    throw new EnumError(value)
                }
                this.map[prop] = value
                Object.defineProperty(this, prop, { value });
            }
        }

        Object.defineProperty(this, 'values', { value: Object.values(this.map) });
    }

    isValid(text: any) {
        if (!text) return true
        return this.values.includes(text)
    }
}

Дело в том, что если я скопирую объект, используемый в конструкторе 4 раза, он даже скажет мне, что FolderVisibility.values имеет тип 'public' | 'private'

PS: Я пробовал это, но это даст мне string за FolderVisibility.values. Кроме того, это все еще довольно долго. const data = {PUBLIC: 'public', PRIVATE: 'private'}

const FolderVisibility = new Enum<typeof data>(data) as Enum<typeof data> & typeof data

person Akxe    schedule 18.04.2018    source источник
comment
Почему вы не можете просто использовать простое машинописное перечисление enum FolderVisibilityEnum { PUBLIC = 'public', PRIVATE = 'private' }? Вы можете создать эти методы для перечисления, и это будет выглядеть лучше ..   -  person Titian Cernicova-Dragomir    schedule 18.04.2018
comment
У нас есть несколько перечислений, которые используют один и тот же класс, поэтому было бы целесообразно использовать одну и ту же функцию (и). Также было бы неплохо, если бы добавление перечисления не потребовало бы особых усилий.   -  person Akxe    schedule 18.04.2018
comment
Второе преимущество - лучше узнать, как использовать универсальные типы в TS. Функций так много, но по ним сложно найти руководства.   -  person Akxe    schedule 18.04.2018


Ответы (1)


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

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

function Enum<T extends{ [P in keyof T]: string }>(enums: T) {

    let map : { [index: string]: string } = {}

    for (let prop in enums) {
        if (enums.hasOwnProperty(prop)) {
            const value = enums[prop]
            if(typeof value != 'string'){
                throw new EnumError(value)
            }
            map[prop] = value;
        }
    }
    let result = Object.assign({}, enums , {
        values: Object.values(map),
        isValid(text: string) {
            if (!text) return true
            return this.values.includes(text)
        }
    });
    // maybe frees the enum so no values are chanegd ?
    return Object.freeze(result);
}
const FolderVisibility = Enum<{
    PUBLIC: 'public',
    PRIVATE: 'private'
}>({
    PUBLIC: 'public',
    PRIVATE: 'private'
});
console.log(FolderVisibility.isValid("")) // Works
console.log(FolderVisibility.PRIVATE === "private" ) // And const fields of string literal type

Мы также можем использовать указанную выше функцию для увеличения фактического перечисления, при этом требуется менее явная типизация:

enum FolderVisibilityProto {
    PUBLIC ='public',
    PRIVATE=  'private'
}
const FolderVisibility = Enum(FolderVisibilityProto);

Или мы можем немного изменить функцию Enun, чтобы она выполняла обратный вызов, который внутренне создает перечисление, чтобы никогда не иметь доступа к нерасширенному перечислению:

function Enum<T extends{ [P in keyof T]: string }>(enumsCreator: () => T) {
    let enums = enumsCreator();
    …
}

const FolderVisibility = Enum(()=> 
{
    enum FolderVisibility {
        PUBLIC ='public',
        PRIVATE=  'private'
    }
    return FolderVisibility;
});
person Titian Cernicova-Dragomir    schedule 18.04.2018