Аннотация типа TypeScript, исключающая примитивы

Вопрос: есть ли способ написать аннотацию типа в TypeScript, которая допускает любой литерал объекта, но не позволяет использовать встроенные типы number, string, boolean, Function или Array?

Почему?

Я работал над улучшением некоторых определений типов в DefinitelyTyped для некоторых библиотек, которые я использую в своих проектах на работе. Обычный шаблон, который я заметил во многих библиотеках JS, заключается в передаче объекта «параметров», используемого для настройки библиотеки или плагина. В этих случаях вы часто будете видеть определения типов, выглядящие примерно так:

declare module 'myModule' {
    interface IMyModule {
        (opts?: IOptions): NodeJS.ReadWriteStream;
    }
    interface IOptions {
        callback?: Function;
        readonly?: boolean;
    }
}

Это позволяет использовать его следующим образом:

var myModule = require('myModule');
myModule();
myModule({});
myModule({ callback: () => {} });
myModule({ readonly: false });
myModule({ callback: () => {}, readonly: false });

Все они являются допустимыми вариантами использования. Проблема в том, что эти определения типов также допускают следующие недопустимые варианты использования:

myModule('hello');
myModule(false);
myModule(42);
myModule(() => {});
myModule([]);

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

Кажется, что если у вас есть интерфейс только с необязательными членами (поскольку ни одна из опций не требуется), компилятор расширит допустимые типы, чтобы принять any.

Вы можете увидеть демонстрацию этой проблемы на TypeScript Playground здесь: http://bit.ly/1Js7tLr

Обновление: пример решения, которое не работает

interface IOptions {
    [name: string]: any;
    callback?: Function;
    readonly?: boolean;
}

Эта попытка требует присутствия оператора индексации в объекте, что означает, что теперь он жалуется на numbers, strings, booleans, Functions и Arrays. Но это создает новую проблему:

var opts = {};
myModule(opts);

Теперь это терпит неудачу, когда это должно быть допустимым сценарием. (См. это в Playground: http://bit.ly/1MPbxfX)


person Joe Skeen    schedule 24.08.2015    source источник
comment
Я видел это: github.com/Microsoft/TypeScript/pull/3823, но Я не уверен, решает ли это проблему или нет, и это v1.6. Я хотел бы найти способ сделать это в 1.5, если это возможно.   -  person Joe Skeen    schedule 24.08.2015
comment
Вам, вероятно, нужно будет просто запустить много проверок, я не представляю ничего элегантного. Все эти случаи могут вам помочь: tobyho.com/2011/01 /28/проверка типов в JavaScript   -  person Zachary Dow    schedule 24.08.2015
comment
@ZacharyDow, здесь я на самом деле не пишу библиотеку JavaScript, а вместо этого пишу определения типов TypeScript, которые описывают API существующей библиотеки JavaScript (см. github.com/borisyankov/DefinitelyTyped). У меня нет контроля над кодом, который в конечном итоге будет вызван. Моя цель — помочь пользователям TypeScript получить ошибки компиляции, если они передают неправильные типы в качестве параметров, чтобы они не ложно предполагали, что он будет работать только потому, что он компилируется. Без этих ошибок времени компиляции пользователи не могут быть уверены в правильности или полезности определений.   -  person Joe Skeen    schedule 24.08.2015


Ответы (2)


Начиная с TypeScript 2.2, появился тип object, который делает почти то же самое, что я описал выше.

... вы можете назначить что угодно типу object, кроме string, boolean, number, symbol и, при использовании strictNullChecks, null и undefined.

Ознакомьтесь с официальным объявлением здесь: Объявление о TypeScript 2.2

person Joe Skeen    schedule 23.02.2017

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

interface IOptions {
    callback?: Function;
    readonly?: boolean;
}

Это фактически эквивалентно «злому интерфейсу»:

interface IOptions {
}

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

Предлагаемое вами решение - это путь...

interface IOptions {
    [name: string]: any;
    callback?: Function;
    readonly?: boolean;
}

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

// If you must
var opts = <IOptions>{};
myModule(opts);

// More typical
myModule({});

// Although... if its empty
myModule();

Отказ от ответственности. Небольшая часть этого всего лишь мое мнение ... но пустой интерфейс - это худшее, что вы можете сделать с интерфейсом в TypeScript, а тот, который ничего не навязывает, - это вторая худшая вещь.

Вы можете избежать дублирования, используя свой собственный базовый интерфейс...

interface Indexed {
    [name: string]: any;
}

Или даже...

interface Indexed<T> {
    [name: string]: T;
}

А затем используйте его на всех ваших интерфейсах опций...

interface IOptions extends Indexed {
    callback?: Function;
    readonly?: boolean;
}
person Fenton    schedule 24.08.2015
comment
Я думаю, вы правы, хотя лично я предпочел бы var opts: IOptions = {}; var opts = <IOptions>{};. Я вижу эти «пустые интерфейсы» повсюду в DefinitelyTyped, и когда я просматриваю тестовые файлы, я вижу вещи, которые проходят компиляцию, но определенно не будут работать. Я надеялся, что есть лучший способ, кажется, что добавление индексатора к каждому интерфейсу «параметров» менее чем оптимально элегантно. Спасибо, что подтвердили это. - person Joe Skeen; 24.08.2015
comment
Если бы только был встроенный интерфейс, содержащий индексатор по умолчанию, вы могли бы расширить его в своем интерфейсе и получить правильное поведение по умолчанию. - person Joe Skeen; 24.08.2015
comment
Я добавил базовый класс в простой и универсальной форме, если вы хотите включить его в свои определения :) - person Fenton; 24.08.2015