typescript 有没有办法定义一个类型/接口来限制键或者扩展的类型/接口?

9rygscc1  于 2023-01-31  发布在  TypeScript
关注(0)|答案(1)|浏览(140)

我有一个经典的接口来定义路由:

interface Routes {
  [key: string]: string | Routes;
}

我喜欢为每个模块定义子接口,例如:

interface GeoRoutes extends Routes {
    country: {
        list: string;
    };
}

到目前为止一切顺利,但我想防止GeoRoutes对象有其他未定义的键。例如,声明this,编译:

const apiRoutes: GeoRoutes = {
    foo: 'bar',
    country: {
        list: '/countries',
    },
};

但我希望不是。
对我来说,扩展父接口的全部意义在于在工作于路由的方法中使用它,例如:

function addRoute(routes: Routes) { ... }
gpfsuwkq

gpfsuwkq1#

这更像是你想对Routes进行 * check * GeoRoutes而不是 * extend * 它。有点像satisfies操作符,但是是针对类型的。我倾向于创建一个名为SatisfiesRoutes<T>的辅助类型,它不改变它的类型,但是如果T是一个无效的Routes类型,它会发出抱怨。可能是这样的:

type SatisfiesRoutes<T extends (ToRoutes<T> extends infer U ? U : never)> = T;

type ToRoutes<T> = T extends object ?
  { [K in keyof T]: string | ToRoutes<T[K]> } : never;

ToRoutes<T>类型获取一个类型,并通过递归地将属性Map到stringToRoutes<>来将其转换为有效的Routes。其思想是如果T extends ToRoutes<T>,则T是有效的Routes;否则,T extends ToRoutes<T>的故障将给出关于T的错误的错误信息。
注意,我很想写type SatisfiesRoutes<T extends ToRoutes<T>> = T,但是编译器认为这是非法循环。由于不支持microsoft/TypeScript#51011中要求的任意循环约束,我需要解决它。通过使用ToRoutes<T> extends infer U ? U : never中的条件类型推理,我延迟了足够长的时间来避免循环检测。
好吧,我们来测试一下:

interface GeoRoutes extends SatisfiesRoutes<{
  country: {
    list: string;
  };
}> { } // okay

interface BadGeoRoutes extends SatisfiesRoutes<{
  country: {
    list: number; // error!
  };
}> { } // Types of property 'list' are incompatible

所以它允许好的类型,不允许坏的类型,并且因为SatisfiesRoutes只返回它的参数,GeoRoutes类型没有索引签名,因此对象常量将根据需要进行额外的属性检查:

const apiRoutes: GeoRoutes = {
  foo: 'bar', // excess property warning
  country: {
    list: '/countries',
  },
};

在您实际上不想担心索引签名的情况下,也可以使用类似T extends ToRoutes<T>约束的行为来验证/无效潜在的Routes类型,如下所示:

function addRoute<T>(routes: T extends ToRoutes<T> ? T : ToRoutes<T>) { }
addRoute({ z: "abc", y: { x: "def" } }); // okay
addRoute({ z: "abc", y: { x: "def", w: 123 } }); // error!
// -------------------------------> ~
// Type 'number' is not assignable to type 'string'.

Playground代码链接

相关问题