TypeScript: Принудительное применение типов параметров класса без вмешательства в ключи

#typescript

Вопрос:

Мне нужно определить схему «операций», которые будут использоваться в моем приложении. Эта схема должна быть расширяемой для других групп «операций». Он должен содержать словарь настроек для каждого ключевого слова.

В какой-то момент «общий вызывающий абонент» должен получить тип «операции» плюс ключевое слово и создать его кэш для последующих вызовов.

Мне также нужен этот «универсальный вызывающий», чтобы убедиться, что запрошенное ключевое слово определено в операции во время компиляции, поэтому оно показывает ошибки разработчикам в коде VS.

Ниже приведено решение, которое очень близко к тому, что мне нужно:

 // Operation interface
interface Operation {
    url: string
    parameters: Record<string,string>
}

// Operations schema
class Operations {}
class BaseOperations extends Operations {   
    one: {
        url: '/one',
        parameters: {p: '1'}
    }
    two: {
        url: '/two',
        parameters: {}
    }
}

// Generic caller (which caches the calls)
function runOperation<T extends Operations>(type: {new(): T;}, keyword: keyof T) {
    let operation = new type();
    //cache_and_run(operation[keyword]);
}

// Main
function main() {
    runOperation(BaseOperations, 'one');
    runOperation(BaseOperations, 'two');
    runOperation(BaseOperations, 'three'); // VS Code error, as expected
}
 

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

После некоторых исследований я нашел параметр «подпись индекса», который позволяет применять возвращаемый тип:

 class BaseOperations extends Operations {
    [x:string]: Operation
    one: {
        url: '/one',
        parameters: {p: '1'}
    }
    two: { // 'uarl' is not assignable
        uarl: '/two'
    }
}
 

Однако этот подход отключил 'one'|'two' проверку runOperation , поскольку любая строка теперь является допустимой keyof BaseOperations .

Есть какие-нибудь предложения?

Ответ №1:

Вместо использования подписи индекса вы можете добавить implements предложение в объявление класса, чтобы попросить компилятор проверить, BaseOperations можно ли назначить реализованный тип. В этом случае я бы использовал самореферентный тип, такой как Record<keyof BaseOperations, Operation> :

 class BaseOperations extends Operations 
    implements Record<keyof BaseOperations, Operation> {

    one!: {
        url: '/one',
        parameters: { p: '1' }
    }
    two!: { // error! 'uarl' is not assignable
        uarl: '/two'
    }

}
 

В приведенном выше есть ошибка при two объявлении. Если вы исправите это, то класс будет компилироваться без ошибок. Между тем компилятор все еще точно знает, какие ключи существуют в a BaseOperations , потому что он не расширил свой набор ключей до string :

 runOperation(BaseOperations, 'one'); // okay
runOperation(BaseOperations, 'two'); // okay
runOperation(BaseOperations, 'three'); // error!
 

Ссылка на игровую площадку для кода

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

1. В точку! Я пытался implements Record<string,Operation> и застрял на той же проблеме, что и раньше. Ссылка на класс из интерфейса, который он реализует, звучит одновременно странно и красиво. Ваше решение работает идеально, большое вам спасибо за потраченное время.