Ответ на другой вопрос касается того, почему непросто автоматизировать защиту типов времени выполнения для интерфейсов времени компиляции (т. е. введите стирание) и какие у вас есть варианты (т. е. генерация кода как в typescript-is
, классы и декораторы как в _ 2_ или объекты схемы, которые можно использовать для создания как защиты типов, так и интерфейсов, как в _ 3_).
Если это важно, я перевел пример кода из этого вопроса на ваш случай. Это один из возможных способов написания кода, который генерирует как защиту типа, так и интерфейсы. Ваша библиотека схем может выглядеть так:
namespace G {
export type Guard<T> = (x: any) => x is T;
export type Guarded<T extends Guard<any>> = T extends Guard<infer V> ? V : never;
const primitiveGuard = <T>(typeOf: string) => (x: any): x is T => typeof x === typeOf;
export const gString = primitiveGuard<string>("string");
export const gNumber = primitiveGuard<number>("number");
export const gBoolean = primitiveGuard<boolean>("boolean");
export const gNull = (x: any): x is null => x === null;
export const gObject =
<T extends object>(propGuardObj: { [K in keyof T]: Guard<T[K]> }) =>
(x: any): x is T => typeof x === "object" && x !== null &&
(Object.keys(propGuardObj) as Array<keyof T>).
every(k => (k in x) && propGuardObj[k](x[k]));
export const gPartial =
<T extends object>(propGuardObj: { [K in keyof T]: Guard<T[K]> }) =>
(x: any): x is { [K in keyof T]?: T[K] } => typeof x === "object" && x !== null &&
(Object.keys(propGuardObj) as Array<keyof T>).
every(k => !(k in x) || typeof x[k] === "undefined" || propGuardObj[k](x[k]));
export const gArray =
<T>(elemGuard: Guard<T>) => (x: any): x is Array<T> => Array.isArray(x) &&
x.every(el => elemGuard(el));
export const gUnion = <T, U>(tGuard: Guard<T>, uGuard: Guard<U>) =>
(x: any): x is T | U => tGuard(x) || uGuard(x);
export const gIntersection = <T, U>(tGuard: Guard<T>, uGuard: Guard<U>) =>
(x: any): x is T & U => tGuard(x) && uGuard(x);
}
Исходя из этого, мы можем построить вашу IExample1
гвардию и интерфейс:
const _isExample1 = G.gObject({
a: G.gNumber,
b: G.gNumber,
c: G.gNumber
});
interface IExample1 extends G.Guarded<typeof _isExample1> { }
const isExample1: G.Guard<IExample1> = _isExample1;
Если вы посмотрите на _isExample1
, вы увидите, что он похож на {a: number; b: number; c: number}
, и если вы посмотрите IExample1
, он будет иметь эти свойства. Обратите внимание, что gObject
Guard не заботится о дополнительных свойствах. Значение {a: 1, b: 2, c: 3, d: 4}
будет допустимым IExample1
; это нормально, потому что типы объектов в TypeScript не являются точными. Если вы хотите, чтобы ваша защита типа принудительно обеспечивала отсутствие дополнительных свойств, вы должны изменить реализацию gObject
(или сделать gExactObject
или что-то в этом роде).
Затем мы создаем защиту и интерфейс ICustomState
:
const _isCustomState = G.gPartial({
example1: isExample1,
e: G.gString,
f: G.gBoolean
});
interface ICustomState extends G.Guarded<typeof _isCustomState> { }
const isCustomState: G.Guard<ICustomState> = _isCustomState;
Здесь мы используем gPartial
, чтобы объект имел только необязательные свойства, как в вашем вопросе. Обратите внимание, что средство защиты gPartial
проверяет объект-кандидат и отклоняет объект только в том случае, если присутствует ключ неправильного типа. Если ключ отсутствует или undefined
, это нормально, поскольку именно это означает необязательное свойство. И, как gObject
, gPartial
не заботится о дополнительных свойствах.
Когда я смотрю на ваш кодcodeandbox, я вижу, что вы возвращаете true
, если присутствует какой-либо из ключей свойств, и false
в противном случае, но это неправильный тест. Объект {}
без свойств может быть назначен типу объекта со всеми необязательными свойствами, поэтому вам не нужно присутствие каких-либо свойств. И наличие ключа само по себе не считается, поскольку объект {e: 1}
не должен быть назначен {e?: string}
. Вам необходимо проверить все свойства, присутствующие в объекте-кандидате, и отклонить его, если какое-либо из свойств имеет неправильный тип.
(Примечание: если у вас есть объект с некоторыми необязательными и некоторыми обязательными свойствами, вы можете использовать пересечение типа G.gIntersection(G.gObject({a: G.gString}), G.gObject({b: G.gNumber}))
, которое будет защищать {a: string} & {b?: number}
, что совпадает с {a: string, b?: number}
.)
Наконец ваш ICustomState[]
охранник:
const isCustomStateArray = G.gArray(isCustomState);
Давайте протестируем этот CustomState
охранник, чтобы увидеть, как он себя ведет:
function testCustomState(json: string) {
console.log(
json + " " + (isCustomState(JSON.parse(json)) ? "IS" : "is NOT") + " a CustomState"
);
}
testCustomState(JSON.stringify({})); // IS a CustomState
testCustomState(JSON.stringify({ e: "" })); // IS a CustomState
testCustomState(JSON.stringify({ e: 1 })); // is NOT a CustomState
testCustomState(JSON.stringify({ example1: { a: 1, b: 2, c: 3 } })); // IS a CustomState
testCustomState(JSON.stringify({ w: "", f: true })); // IS a CustomState
Думаю, все в порядке. Единственный неудачный пример - {e:1}
, потому что его свойство e
имеет неправильный тип (number
вместо string | undefined
).
В любом случае, надеюсь, это поможет; удачи!
площадка ссылку на код
person
jcalz
schedule
18.01.2020
ICustomState
, поэтому не знаю, что посоветовать. Удачи! - person jcalz   schedule 17.01.2020