[英]Typescript: Assert unknown input has type Pick<ConcreteType, subset of keys of ConcreteType> for specified keys
当尝试创建一个通用的 function 来测试未知输入是否是已知 object 类型的子集时,我遇到了 Typescript 的问题。我想指定应该存在哪些键并断言输入的类型为 Pick<ConcreteType, subset ConcreteType> 的键数。 我的主张
简化代码:
type Rectangle = {
width: number,
height: number
}
const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
o,
) => {
if (typeof o !== `object`) {
throw new Error();
}
};
function assertRectangle <K extends keyof Rectangle>(o: unknown, ...keys: K[]): asserts o is Pick<Rectangle, K> {
assertObject(o);
// >>>>>>>>>>>>>>>>>>>>>> HERE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
if (keys.includes(`width` as K) && !o.hasOwnProperty('width')) {
throw new Error('');
}
if (keys.includes(`height` as K) && !o.hasOwnProperty('height')) {
throw new Error('');
}
}
const rect = {width: 1, height: 1};
assertRectangle(rect, 'width'); // Pick<Rectangle, "width">
assertRectangle(rect, 'height', 'width'); // Pick<Rectangle, "height" | "width">
这段代码有效,但如果我们删除keys.includes
中的as K
则无效。
if (keys.includes(`width`) && !o.hasOwnProperty('width')) {
throw new Error('');
}
// OR:
if (keys.includes(`width` as const) && !o.hasOwnProperty('width')) {
throw new Error('');
}
“宽度”类型的参数不可分配给“K”类型的参数。 “宽度”可分配给类型“K”的约束,但“K”可以用约束“keyof Rectangle”的不同子类型实例化。ts(2345)
我想知道为什么as const
在这里不起作用,我想知道的问题是当我或同事决定更改或重命名 Rectangle 类型的属性时,我希望 typescript 在这个断言不再涵盖时警告我方式。 添加、重命名或减去类型的属性不会被此断言捕获。
在评论中你说:
我这样做是因为每个属性/键都可以有单独的检查。 例如 Rectangle 可以有一个 id 字段,我还想断言该 id 是否属于字符串类型并匹配 uuid 正则表达式
在那种情况下,我会这样处理:
遍历keys
检查它们是否存在
对于需要额外检查的属性,使用if
/ else if
来识别它们并应用额外检查。 (我很惊讶地发现switch
不会抱怨永远无法到达的情况,但if
会的话。)
类似于以下内容 - 请注意,在此示例中,我检查了Rectangle
上不存在的属性,TypeScript 就此警告我。 这是为了证明您的“我想知道的是,当我或同事决定更改或重命名 Rectangle 类型的属性时”的情况(在这种情况下,假设Rectangle
曾经有id
但现在没有了)。
type Rectangle = {
width: number,
height: number;
};
const assertObject: (o: unknown) => asserts o is Record<PropertyKey, unknown> = (
o,
) => {
if (o === null || typeof o !== `object`) {
// ^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− added
throw new Error();
}
};
function assertRectangle<K extends keyof Rectangle>(
o: unknown,
...keys: K[]
): asserts o is Pick<Rectangle, K> {
assertObject(o);
for (const key of keys) {
// Basic check
if (!o.hasOwnProperty(key)) {
throw new Error("");
}
// Additional per-property checks
// (I was surprised that `switch` didn't work here to call out property
// names that aren't on Rectangle like "id" below.)
if (key === "width" || key === "height") {
if (typeof o[key] !== "number") {
throw new Error(`typeof of '${key}' expected to be 'number'`);
}
} else if (key === "id") {
// ^^^^^^^^^^^^−−−−−−−− causes error because `id` isn't a valid
// Rectangle property (e.g., if you remove
// a property from `Rectangle`, TypeScript
// warns you)
const id = o[key];
if (typeof id !== "string" || !id) {
throw new Error(`'${key}' expected to be non-empty string`);
}
}
}
}
declare let rect1: unknown;
declare let rect2: unknown;
declare let rect3: unknown;
assertRectangle(rect1, "width");
rect1; // <== type is Pick<Rectangle, "width">
assertRectangle(rect2, "height", "width");
rect2; // <== type is Pick<Rectangle, "height" | "width">
assertRectangle(rect3, "height", "width", "not-rectangle-property");
// Error −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.