#typescript #types
#typescript #типы
Вопрос:
У меня interface
есть обычно регистрируемый (это распределенный контекст трассировки). В моем проекте также есть определенный тип для представления секретов, и я хочу убедиться, что объекты типа Secret
никогда не добавляются к этому interface
.
Как я могу ограничить тип любых новых свойств, добавленных в interface
?
Например, вот как выглядит текущий интерфейс:
interface TraceMetadata {
foo: string;
bar: number;
time: Date;
}
Я хотел бы иметь гарантию на уровне типа, что кто-то этого не сделает:
interface TraceMetadata {
foo: string;
bar: number;
time: Date;
userSecret: Secret; // bad!
}
Как я могу это сделать в TypeScript?
Ответ №1:
Насколько я знаю, в TypeScript нет никакого механизма, который предотвратил бы это напрямую. Если бы компилятор разрешил вам использовать объявления интерфейса с самоссылками, вы могли бы добиться этого, но, похоже, TS4.0 и выше не допускают этого (см. microsoft / TypeScript #40315 для получения дополнительной информации).
Вместо этого вы могли бы придумать псевдоним типа sentinel, который будет выдавать предупреждение компилятора тогда и только тогда, когда кто-то добавит свойство a Secret
-valued в TraceMetadata
интерфейс. Например:
type VerifyExtends<T, U extends T> = void;
type ValueOf<T> = T[keyof T];
type TraceMetadataProperties = ValueOf<TraceMetadata>;
type AcceptableTraceMetadataProperties = Exclude<TraceMetadataProperties, Secret>;
/* NOTE WELL! */
// If there is an error in the next line of code, then someone has put a Secret
// value into TraceMetadata. You should find out who and tell them to
// be ashamed of themselves.
type TraceMetadataHasNoSecrets = VerifyExtends<AcceptableTraceMetadataProperties,
TraceMetadataProperties // <-- error here if someone has been naughty
>;
VerifyExtends<T, U>
Тип позволит вам указать только a T
и U
введите where U extends T
. Тип TraceMetadataProperties
— это объединение всех типов свойств в TraceMetadata
, в то время AcceptableTraceMetadataProperties
как этот тип с удаленными Secret
значениями. Если TraceMetadataProperties extends AcceptableTraceMetadataProperties
, то все в порядке, так как каждое свойство TraceMetadata
является приемлемым. В противном случае существует свойство TraceMetadata
, которое неприемлемо, и вы получите сообщение об ошибке.
Тот факт, что ошибка появляется внутри этого псевдонима типа sentinel, а не в оскорбительных свойствах TraceMetadata
, является неоптимальным. Но если вы прокомментируете тип sentinel достаточно, надеюсь, кто-нибудь сможет понять, что означает предупреждение.
Давайте проверим это.
С этим определением нет предупреждений компилятора:
interface TraceMetadata {
foo: string;
bar: number;
time: Date;
}
type TraceMetadataHasNoSecrets = VerifyExtends<AcceptableTraceMetadataProperties,
TraceMetadataProperties // okay
>;
Но с этим определением существует:
interface TraceMetadata {
foo: string;
bar: number;
time: Date;
userSecret: Secret; // bad!
}
type TraceMetadataHasNoSecrets = VerifyExtends<AcceptableTraceMetadataProperties,
TraceMetadataProperties // error!
//~~~~~~~~~~~~~~~~~~~~~~~ <--
// Type 'ValueOf<TraceMetadata>' does not satisfy the constraint
// 'string | number | Date'.
>;
Надеюсь, в случае, когда на самом деле есть предупреждение компилятора, кто-нибудь посмотрит на ошибку типа sentinel и увидит error here if someone has been naughty
someone has put a Secret value into TraceMetadata
и сможет исправить проблему.
Комментарии:
1. Приятно! Есть ли способ расширить эту проверку, чтобы охватить случай, когда некоторые свойства сами могут быть объектами, и в этом случае нам нужно будет убедиться, что ни одно из их свойств не является секретным? Я поиграл с
ContainsSecret
проверкой типа, но она работает не так, как я ожидал: « type ContainsString<T> = T расширяет строку? T : (T расширяет {[k: string]: ContainsString<T>} ? T : никогда); const a: ContainsString<число> = 2; // это (неожиданно) проверяет тип «