TypeScript 函数根据输入参数返回类型

问题描述 投票:0回答:6

我有一些不同的接口和对象,每个接口和对象都有一个

type
属性。假设这些是存储在 NoSQL 数据库中的对象。如何根据其输入参数
getItem
创建具有确定性返回类型的通用
type
函数?

interface Circle {
    type: "circle";
    radius: number;
}

interface Square {
    type: "square";
    length: number;
}

const shapes: (Circle | Square)[] = [
    { type: "circle", radius: 1 },
    { type: "circle", radius: 2 },
    { type: "square", length: 10 }];

function getItems(type: "circle" | "square") {
    return shapes.filter(s => s.type == type);
    // Think of this as items coming from a database
    // I'd like the return type of this function to be
    // deterministic based on the `type` value provided as a parameter. 
}

const circles = getItems("circle");
for (const circle of circles) {
    console.log(circle.radius);
                       ^^^^^^
}

“圆 | 类型”上不存在属性“半径”方形'.

typescript
6个回答
255
投票

Conditional Types
来救援:

interface Circle {
    type: "circle";
    radius: number;
}

interface Square {
    type: "square";
    length: number;
}

type TypeName = "circle" | "square"; 

type ObjectType<T> = 
    T extends "circle" ? Circle :
    T extends "square" ? Square :
    never;

const shapes: (Circle | Square)[] = [
    { type: "circle", radius: 1 },
    { type: "circle", radius: 2 },
    { type: "square", length: 10 }];

function getItems<T extends TypeName>(type: T) : ObjectType<T>[]  {
    return shapes.filter(s => s.type == type) as ObjectType<T>[];
}

const circles = getItems("circle");
for (const circle of circles) {
    console.log(circle.radius);
}

感谢 Silvio 为我指明了正确的方向。


85
投票

您正在寻找超载签名

function getItems(type: "circle"): Circle[]
function getItems(type: "square"): Square[]
function getItems(type: "circle" | "square") {
    return shapes.filter(s => s.type == type);
}

在实际定义之前放置多个类型签名可以让您列出函数签名可能属于的不同“情况”。

发表评论后进行编辑

事实证明,您想要的是可能,但我们可能需要克服一些困难才能到达那里。

首先,我们需要一种方法来翻译每个名字。我们希望

"circle"
映射到
Circle
"square"
Square
,等等。为此,我们可以使用 条件类型

type ObjectType<T> =
  T extends "circle" ? Circle :
  T extends "square" ? Square :
  never;

(我使用

never
作为后备,希望如果您以某种方式最终得到无效类型,它会很快创建类型错误)

现在,我不知道如何像您所要求的那样对函数调用的类型进行参数化,但是 Typescript 确实 支持通过 映射类型 对对象的键进行参数化。因此,如果您愿意用

getItems("circle")
语法换取
getItems["circle"]
,我们至少可以描述类型。

interface Keys {
  circle: "circle";
  square: "square";
}

type GetItemsType = {
  [K in keyof Keys]: ObjectType<K>[];
}

问题是,我们现在必须实际构造一个这种类型的对象。如果您的目标是 ES2015(编译时为

--target es2015
或更新版本),则可以使用 Javascript
Proxy
类型。现在,不幸的是,我不知道有什么好方法可以让 Typescript 相信我们正在做的事情是可以的,因此通过
any
进行快速转换将平息其担忧。

let getItems: GetItemsType = <any>new Proxy({}, {
  get: function(target, type) {
    return shapes.filter(s => s.type == type);
  }
});

因此,您失去了对实际

getItems
“函数”的类型检查,但您在调用站点获得了更强的类型检查。然后,拨打电话,

const circles = getItems["circle"];
for (const circle of circles) {
    console.log(circle.radius);
}

这值得吗?这取决于你。这是很多额外的语法,您的用户必须使用

[]
表示法,但它会得到您想要的结果。


31
投票

我遇到了类似的问题。如果您不想同时拥有

TypeName
ObjectType
类型,也可以使用单个界面来完成:

interface TypeMap {
  "circle": Circle;
  "square": Square;
}

function getItems<T extends keyof TypeMap>(type: T) : TypeMap[T][]  {
  return shapes.filter(s => s.type == type) as TypeMap[T][];
}

2
投票

我建议使用辅助类型从字符串转换为对象

type AtoB<A, B> = B extends { type: infer V } ? V extends A ? B : never : never;

然后可以像 Arash Motamedi 的答案一样使用它来最后转换类型

return shapes.filter(s => s.type == type) as AtoB<T, Circle | Square>[];

您还可以将其用作过滤器内的

Type Guard
,如下所示:

function getItems<T extends TypeName, U extends AtoB<T, Circle | Square>>(type: T) {
    return shapes.filter((s): s is U => s.type == type);
}

这可能更安全一点


-9
投票

我看到您试图通过创建通用函数来保存一些代码,但现在代码的可读性较差,并且变得更加复杂。 如果我们知道“getItems”应该返回一个圆,只需创建一个“getCircles”函数,它返回相应的类型。


-9
投票

正如您所提到的,数据来自具有类型属性的 No-Sql 数据库。您可以将类型属性创建为字符串值,并将接口更改为类以检查函数中的instanceOf。

class Circle {
    type: string;
    radius: number;
}

class Square {
    type: string;
    length: number;
}

const shapes: (Circle | Square)[] = [
    { type: "circle", radius: 1 },
    { type: "circle", radius: 2 },
    { type: "square", length: 10 }];

function getItems(type: string) {
    return shapes.filter(s => s.type == type);
    // Think of this as items coming from a database
    // I'd like the return type of this function to be
    // deterministic based on the `type` value provided as a parameter. 
}

const circles = getItems("circle");
for (const circle of circles) {
    if (circle instanceof Circle) {
        console.log(circle.radius);
    } else if (circle instanceof Square) {
        console.log(circle.length);
    }
} 
© www.soinside.com 2019 - 2024. All rights reserved.