打字稿:断言未知输入具有指定键的类型 Pick<ConcreteType,ConcreteType 键的子集>



当尝试创建一个泛型函数来测试未知输入是否是已知对象类型的子集时,我在使用Typescript时遇到了麻烦。我想指定应该出现哪些键,并断言输入类型为Pick<ConcreteType,即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('');
}

类型为'"width"'的实参不能赋值给类型为'K'的形参。' width '可以赋值给类型为'K'的约束,但是'K'可以用约束'keyof Rectangle'的另一个子类型实例化。ts(2345)

我想知道为什么as const在这里不起作用,我想知道的问题是,当我或同事决定更改或重命名矩形类型的属性时,我希望typescript在此断言不再覆盖类型时警告我。在类型上添加、重命名或减去属性将不会被此断言捕获。

你在评论中说:

我这样做是因为每个属性/键可以有单独的检查。例如,矩形可以有一个id字段我还想断言id是否为字符串类型并匹配uuid正则表达式

在这种情况下,我将这样处理它:

  1. 通过keys循环检查它们是否存在

  2. 对于需要额外检查的属性,使用if/else if来识别它们并应用额外的检查。(我很惊讶地发现switch不会抱怨那些永远无法到达的情况,但if会。)

就像下面这样——注意,在这个例子中,我检查了一个在Rectangle上不存在的属性,TypeScript会警告我。这是为了演示你的"我想知道的是,当我或同事决定改变或重命名属性的类型矩形">的情况(在这种情况下,让我们说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 −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^

操场上联系

最新更新