既然TypeScript支持通过in
运算符和satisfies
关键字进行类型窄化,从version 4.9开始,我一直在寻找一种标准化的方法来以类型安全的方式编写类型 predicate 函数,而不使用任何类型Assert。
这样,如果类型定义更改,TypeScript将知道该类型的typeguard函数不再正确。
然而,我遇到了一些问题,看起来TypeScript正确地确定了每个对象属性的类型,但没有正确地缩小对象本身的类型。
下面是一个最小可重复的示例:
type MyType = {
foo: string;
};
function isMyType(data: unknown): data is MyType {
if (!(
typeof data === 'object' &&
data !== null
)) {
return false;
}
if (!(
'foo' in data &&
typeof data.foo === 'string'
)) {
return false;
}
data.foo; // <- Typed correctly as `string`
data; // <- Typed incorrectly as `object & Record<"foo", unknown>`
/* Error:
Type 'object & Record<"foo", unknown>' does not satisfy the expected type 'MyType'.
Types of property 'foo' are incompatible.
Type 'unknown' is not assignable to type 'string'. */
data satisfies MyType;
return true;
}
TypeScript Playground
正如我在那个例子中所评论的,在使用in
运算符缩小data
的类型之后,TypeScript理解它具有foo
属性。
然而,尽管foo
属性的类型已经使用typeof
操作符缩小到string
,但对象的总体类型仍然是object & Record<"foo", unknown>
而不是object & Record<"foo", string>
。
不过,我发现特别令人困惑的是,如果我访问data.foo
,那么TypeScript完全理解其类型是string
。
到目前为止,我能想到的唯一解决方案是创建一个额外的类型 predicate 函数来告诉TypeScript如何缩小data
的属性类型,如下所示:
type MyType = {
foo: string;
};
function isMyType(data: unknown): data is MyType {
if (!(
typeof data === 'object' &&
data !== null
)) {
return false;
}
if (!(
'foo' in data &&
propertyIsType(data, 'foo', isString)
)) {
return false;
}
data.foo; // <- Typed correctly as `string`
data; // <- Typed as `object & Record<"foo", unknown>` & Record<"foo", string>
// No error
data satisfies MyType;
return true;
}
/**
* Narrow the type of an object based on a test on a particular property's type
*/
function propertyIsType<
R, PropName extends string | number | symbol, T
>(
data: R & Record<PropName, unknown>,
propName: PropName,
predicate: (prop: unknown) => prop is T
): data is R & Record<PropName, T> {
return predicate(data[propName]);
}
/**
* Utility type predicate for the `string` type
*/
function isString(data: unknown): data is string { return typeof data === 'string'; }
TypeScript Playground
但是这个解决方案对我来说并不好,因为它需要一个实用程序propertyIsType
函数和实用程序类型 predicate 函数,如isString
,用于每个需要测试的类型,甚至是基本类型。
我的propertyIsType
实现没有替换类型的Record<"foo", unknown>
部分,而是只添加了Record<"foo", string>
,这也让人感觉有点混乱,但至少这没有引起任何问题。
有没有更好的解决方案来缩小Record<"foo", unknown>
到Record<"foo", string>
这样的类型?
1条答案
按热度按时间k2fxgqgv1#
您可以将检查简化为