为什么赢得的打字稿解决了对象文字声明中函数的返回类型?

时间:2018-05-11 18:17:51

标签: typescript

我在打字稿中做了一些实验,试图设置一个帮助器来布局路线层次结构。

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。

3 个答案:

答案 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的类型正确推断,但未正确显示。

这个解决方案有点麻烦,但有效。