我在打字稿中做了一些实验,试图设置一个帮助器来布局路线层次结构。
const route = <TParams = {}, TChildren = {}>(path: string, children?: TChildren) => ({
path,
params: undefined as TParams,
children
});
我会像这样使用它:
const routes = {
LOGIN: route<MyParams>("/login", {
REGISTER: route("/register")
})
};
我希望routes
的类型最终会像这样结束:
{
LOGIN: {
path: string;
params: MyParams;
children: {
REGISTER: {
path: string;
params: {};
children: {};
}
}
}
}
但相反,我明白了:
{
LOGIN: {
path: string;
params: MyParams;
children: {}
}
}
REGISTER
函数正确返回route
的类型。例如。我可以将route("/register")
分配给变量,但它的类型正确,但如果我尝试将其嵌套在子项中,则routes
只会将REGISTER
显示为any
。
如果我摆脱MyParams
类型参数,那么它变为:
{
LOGIN: {
path: string;
params: {};
children: {
REGISTER: any;
}
}
}
有谁能告诉我这里发生了什么,以及如何实现我的预期行为?
我使用的是Typescript 2.8.1。
答案 0 :(得分:3)
部分问题在于,即使使用default generic parameters,TypeScript目前也有all-or-nothing approach to generic parameter inference。如果您指定一些参数而忽略其他参数,则会获得默认值,而不是推断值。
这是我通常建议人们使用currying将函数拆分为两个函数的地方,其中一个函数是显式设置参数,另一个是依赖于推理。例如:
const route = <TParams>() => <TChildren>(path: string, children?: TChildren) => ({
path,
params: undefined! as TParams,
children
});
在这种情况下,route
是一个返回函数的函数。这就是你怎么称呼它:
const routes = {
LOGIN: route<MyParams>()("/login", {
REGISTER: route()("/register")
})
};
这个额外的电话有点奇怪,但让我们检查routes
的类型:
{
LOGIN: {
path: string;
params: MyParams;
children: {
REGISTER: any;
} | undefined;
};
}
很好,对吗?请注意,您获得了undefined
,因为children
是可选的,因此可能是undefined
。
哦,any
仍在那里。实际上,这似乎只是type display bug,其中IntelliSense放弃了涉及一定量嵌套推理的处理类型。实际类型计算正确:
const children = routes.LOGIN.children;
如果您检查children
,则显示为:
const children: {
REGISTER: {
path: string;
params: {};
children: {} | undefined;
};
} | undefined
这是正确的类型。显示错误是一种耻辱,但如果你了解它,你可以解决它。
希望有所帮助;祝你好运!
答案 1 :(得分:1)
TypeScript目前不支持此功能:https://github.com/Microsoft/TypeScript/issues/10571
您必须指定这两种类型。目前,如果您指定一个泛型类型,它将使用默认类型而不是使用推理 - 也就是说,如果指定另一个类型,则无法推断出一种泛型类型。
唯一的方法是指定两种泛型类型或更新函数以将参数作为参数传递,以便可以推断出这两种类型。
const registerRoutes = {
REGISTER: route("/register"),
};
const routes = {
LOGIN: route<MyParams, typeof registerRoutes>("/login", registerRoutes)
};
答案 2 :(得分:1)
它不起作用,因为通过使用默认类型参数定义类型,当您不提供第二个参数时,它将采用其默认值{}
。 {REGISTER: route('/register')}
可分配给{}
,因此您不会收到错误,但当然会丢失类型信息。
要使其工作,您应该让TypeScript推断出TChildren
的类型。但是,如果删除默认参数,则会出现错误,因为没有默认值的类型参数应该是第一个。如果您更改了订单,则无法在不给TParams
的情况下提供TChildren
值。因此,最好的方法是让TypeScript推断两个参数而不是用它们调用它们。为此,您需要使用一个小技巧,并传递具有该类型的对象,以便可以推断出它。它可以是一个空对象:
const route = <TParams, TChildren>(type: TParams, path: string, children?: TChildren) => ({
path,
params: undefined as TParams,
children,
});
你可以这样称呼:
const routes = {
LOGIN: route({} as MyParams, "/login", {
REGISTER: route({} as MyParams, "/register", null)
})
};
现在,正如@jcalz所说,routes.LOGIN.children
的类型正确推断,但未正确显示。
这个解决方案有点麻烦,但有效。