我有以下打字代码片段:

type Query = {
  model: string;
  on: string;
  fields: string[];
  query?: Query[];
};

function getModel(config: {
  object: xmlrpc.Client
  db: string
  userId: number
  password: string
}) {
  return <T extends { [key: string]: any }>(
    model: string,
    fields: Extract<keyof T, string>[],
    props?: {
      offset?: number
      limit?: number
      where?: [string, string, any][]
      query?: Query[]
    }
  ): Promise<(T & { id: number })[]> =>
    new Promise((resolve, reject) => {
      // Implementation details
    }).then(async (res) => {
      // Post-processing logic
    });
}

当调用getModel时,result的推断类型不符合我的期望.下面是我如何调用该函数:

const config = { object, db, userId, password };

const result = await getModel(config)(
  "product.template",
  ["name", "categ_id", "attribute_line_ids", "service_type"],
  {
    limit: 5,
    where: [["categ_id", "=", 40]],
    query: [
      {
        model: "product.template.attribute.line",
        on: "attribute_line_ids",
        fields: ["value_ids"],
        query: [
          {
            model: "product.attribute.value",
            on: "value_ids",
            fields: ["name"],
          },
        ],
      },
    ],
  }
);

我预计result的类型是:

const result: ({
  name: any
  categ_id: any
  attribute_line_ids: ({ value_ids: ({ name: any } & { id: number })[] } & {
    id: number
  })[]
  service_type: any
} & { id: number })[]

如何调整泛型类型定义以确保推断的类型与预期 struct 匹配?

推荐答案

为了使其工作,您需要getModel()在输入的query属性的类型中为generic,并且在fields的类型中为generic.让我们为查询形状命名,以便可以重用它:

interface Query {
    model: string;
    on: string;
    fields: string[];
    query?: Query[];
};

然后,当你用泛型类型Q[]和泛型类型K[]fields调用getModel()时,你希望输出的类型是Queried<Q, K>,这最终将是递归的,这样我们就可以深入到Q. 这里有一种可能的写法:

type Queried<Q extends Query, K extends string> = [K] extends [never] ? any :
    ({ [P in K]:
        Queried<NonNullable<Extract<Q, { on: P }>["query"]>[number],
            Extract<Q, { on: P }>["fields"][number]>
    } & { id: number })[]

如果查询没有字段K,我们做的第一件事就是返回any,这是我们递归的基本情况.否则,我们需要生成一个对象数组,该数组具有数字id属性以及K中的所有属性.对于K中的每个属性键P,我们需要找到Q的成员(可以是union type),其on字段对应于P,如果它存在的话.我用the Extract utility type来做这件事.然后在该成员上递归,将其query属性的元素用作新的Q,将其fields属性的元素用作新的K.

哦,getModel()看起来是这样的(我删除了它的config输入,因为这个演示不需要它):

function getModel() {
    return <K extends string, const Q extends Query>(
        model: string,
        fields: K[],
        props?: {
            offset?: number
            limit?: number
            where?: [string, string, any][]
            query?: Q[]
        }
    ): Promise<Queried<Q, K>> =>
        new Promise((resolve, reject) => {
            throw 1;
        }).then(async (res) => {
            throw 2;
        });
}

让我们在您的示例中对其进行测试.我们打出这样的电话:

const result = await getModel()(
    "product.template",
    ["name", "categ_id", "attribute_line_ids", "service_type"],
    {
        limit: 5,
        where: [["categ_id", "=", 40]],
        query: [
            {
                model: "product.template.attribute.line",
                on: "attribute_line_ids",
                fields: ["value_ids"],
                query: [
                    {
                        model: "product.attribute.value",
                        on: "value_ids",
                        fields: ["name"],
                    },
                ],
            },
        ],
    }
);

并且结果是类型

/* const result: ({
    name: any;
    categ_id: any;
    attribute_line_ids: ({
        value_ids: ({
            name: any;
        } & {
            id: number;
        })[];
    } & {
        id: number;
    })[];
    service_type: any;
} & {
    id: number;
})[] */

这正是你想要的.


请注意,可以try 增加此类型以允许在对象树的叶 node 上使用一些非any类型,或者让它从一些已知的对象类型中挑选,但这超出了本问题的范围.

Playground link to code

Typescript相关问答推荐

typescript抱怨值可以是未定义的,尽管类型没有定义

为什么typescript要把这个空合并转换成三元?

TypeScript Page Routing在单击时不呈现子页面(React Router v6)

如何访问Content UI的DatePicker/中的sx props of year picker?'<>

React typescribe Jest调用函数

有没有办法解决相互影响的路由之间的合并?

泛型类型联合将参数转换为Never

返回具有递归属性的泛型类型的泛型函数

适当限制泛型函数中的记录

在实现自定义主题后,Angular 不会更新视图

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

以Angular 自定义快捷菜单

从泛型类型引用推断的文本类型

APP_INITIALIZER在继续到其他Provider 之前未解析promise

编译TypeScrip项目的一部分导致错误

类型推断对React.ForwardRef()的引用参数无效

如何在不违反挂钩规则的情况下动态更改显示内容(带有状态)?

打印脚本中正则函数和函数表达式的不同类型缩小行为

React中的效果挂钩在依赖项更新后不会重新执行

如何确定单元格中的块对 mat-h​​eader-cell 的粘附(粘性)?