内部类型的泛型 Typeguard

Generic Typeguard for Inner type

提问人:Martin Hansson 提问时间:6/4/2023 最后编辑:Martin Hansson 更新时间:9/21/2023 访问量:54

问:

所以我遇到了一些问题,在工作中我们使用这些类型:

export type GeoJSON = Geometry | Feature;

export type GeoJsonTypes = GeoJSON['type'];

export interface GeoJsonObject { type: GeoJsonTypes; }

export interface Point extends GeoJsonObject {
   type: 'Point';
   coordinates: number[];
 }

export interface MultiPoint extends GeoJsonObject {
   type: "MultiPoint",
   coordinates: number[][];
 }

export type Geometry =
 | Point
 | MultiPoint

export interface Feature<G extends Geometry = Geometry>
  extends GeoJsonObject {
  type: 'Feature';
  geometry: G;
}

我们遇到的问题是,当我们想要缩小范围时,我们最终不得不在调用站点为每个功能创建一个 Typeguard。Feature<Geometry>

export function isPolygon(f: Feature): f is Feature<Polygon> {
  return f && f.geometry && f.geometry.type === 'Polygon';
}

我想做一个通用的typeguard,我使用Generic Typeguard作为我思考的基础,但我被卡住了。

我知道我可以使用 来获取我的类型,例如这个例子。Extract<Geometry, {type: U}>

export function isFeature<T extends Feature<Geometry>, U extends T['geometry']['type']>(
  feature: T,
  type: U
): feature is Feature<Extract<Geometry, { type: U }>> {
  return feature && feature.geometry && feature.geometry.type === type;
}

我遇到的问题是,如果我将 T 限制为 Feature 类型,则表达式中的 T 变量没有用处,它会给我一个错误,说,如果我将其限制为 Geometry,那么我不能使用 Cause,有没有办法为功能提供通用类型保护?A type Predicate's type must be assignable to it's parameter typeFeature<Extract<T, {type: U}>>T could be instantiated with an arbitrary type which could be unrelated

TypeScript GeoJSON Typeguards 类型缩小

评论


答:

1赞 wonderflame 6/4/2023 #1

问题在于,在类型 guard 中,您键入 as 并检查是否是 以外的其他内容,这在逻辑上是不可能转换为 的。相反,您应该只接受并保留非泛型,只是 .featureTfeatureTTFeature<Extract<Geometry, { type: U }>>Ufeatureunknown

isFeature<U extends Feature<Geometry>["geometry"]["type"]>(
  feature: unknown,
  type: U
): feature is Feature<Extract<Geometry, { type: U }>> {}

这将导致许多重复检查,为了避免它们,我们可以创建一个泛型类型保护,它将任何对象类型转换为 .将属性转换为并使整个对象成为部分对象的原因是为了使其尽可能类型安全:isObjectTPartial<Record<keyof T, unknown>>unknown

const isObject = <T>(
  arg: unknown
): arg is Partial<Record<keyof T, unknown>> => {
  return !!arg && typeof arg === "object" && !Array.isArray(arg);
};

接下来,我们可以为通用特征提取类型保护,理想情况下,您还将包括 into 检查:geometry.coordinates

const isFeatureBase = (arg: unknown): arg is Feature => {
  return (
    isObject<Feature>(arg) &&
    isObject<Feature["geometry"]>(arg.geometry) &&
    typeof arg.type === "string" &&
    typeof arg.geometry.type === "string"
  );
};

最后,我们将如下所示:isFeature

function isFeature<U extends Feature<Geometry>["geometry"]["type"]>(
  feature: unknown,
  type: U
): feature is Feature<Extract<Geometry, { type: U }>> {
  return isFeatureBase(feature) && feature.geometry.type === type;
}

用法:

const a = {};
if (isFeature(a, "MultiPoint")) {
  // "MultiPoint"
  a.geometry.type;
}

操场

评论

0赞 Martin Hansson 6/4/2023
非常感谢它的工作!让我看看我是否理解这一点,我们的 IsObject 正在检查我们是否正在传递一个对象,并且它不是一个数组,我不明白 !!arg 这有什么用?最后,我们的 typeguard 返回 T 的所有键的记录,映射到未知,但可选,因为这将涵盖所有键,如果我们没有它,它可能会丢失?然后,featureIsBase 使用 isObject 约束为 Feature,并传递 arg,geometry 也是如此,其中我们反对 Feature['geometry'] 并传递 args.geometry,最后我们检查两者是否都类型为字符串
1赞 wonderflame 6/4/2023
我们需要 because 是 true,数组也是 ,因此我们检查 是否也不是数组。我们使 optional 和 unknown 成为 internal,因为我们不检查属性的有效性。如果它们丢失或类型不正确怎么办?!!argtypeof null === 'object'objectargIsObject
0赞 Martin Hansson 6/4/2023
哦,好吧,所以基本上它不是 null 并且它不是一个数组!顺便说一句,我修改了你的例子,不采取未知,而是采取功能,这似乎工作得很好,这有什么缺点吗?export function isFeature<U extends Feature<Geometry>["geometry"]["type"]>( feature: Feature, type: U ): feature is Feature<Extract<Geometry, { type: U }>> { return feature && feature.geometry && feature.geometry.type === type; }
1赞 wonderflame 6/4/2023
如果您收到来自 API 的 API,它将不起作用,您应该首先验证该 API。你将不得不写这样的东西:featureisFeature(feature as Feature)
0赞 Martin Hansson 6/4/2023
啊,知道了,非常感谢你的帮助!
0赞 neurosie 9/21/2023 #2

我自己也遇到了这个问题,并想分享一下,如果您已经验证了对象是特征,您只想缩小几何类型,该如何做到这一点:

function isFeature<T extends Feature["geometry"]["type"]>(
  feature: Feature,
  type: T,
): feature is Feature<Extract<Geometry, { type: T }>> {
  return feature.geometry.type === type;
}

if (isFeature(foo, "Polygon")) {/* narrows foo to Feature<Polygon> */}

这适用于 @types/geojson