打字稿动态类型和泛型

时间:2019-09-23 09:32:40

标签: typescript typescript-generics

我很难尝试围绕打字稿动态(和通用)类型进行思考。

我要完成的工作是创建一个函数,该函数返回具有特定类型的对象,该对象的某些属性必须与为该函数提供的任何参数相匹配。

所以,基本上我想发生的事情(伪):

const listRequest = createRequest('list', {ids: [1, 2]});

此函数应该为我创建一个对象,如下所示:

{
  operationVersion: 1,
  protocolVersion: 2,
  operation: 'list', // first param
  list: {            // prop name must match with first param
    ids: [1, 2],     // second param
  }
}

目前,我的代码如下:

interface IBaseRequest {
  operationId: number;
  protocolVersion: number;
  operation: string;
  authenticationToken?: string;
}

export type BaseRequest<Operation extends string> = {
  [Prop in keyof IBaseRequest]: IBaseRequest[Prop];
} & Record<Operation, any>;

type CreateRequestType = <T extends string>(operation: string, params: any) => BaseRequest<T>;

export const createRequest: CreateRequestType = <T extends string>(operation: string, params: any) => {

  const req = {
    operation: operation,
    operationId: 1,
    protocolVersion: 2,
  };

  req[operation] = params;
  return req as BaseRequest<T>;
};

现在,当使用以下命令创建我的请求对象时:

const listRequest = createRequest('list', {a: 'aa'});

我没有得到listRequest.list的智能感知,也没有得到listRequest的类型是BaseRequest<'list'>

如果尝试使用以下方法创建请求:

type ListRequest = 'list';
const test = <ListRequest>createRequest('list', {a: 'aa'});

我得到一个错误:

Conversion of type 'BaseRequest<string>' to type '"list"' may be a mistake
because neither type sufficiently overlaps with the other. If this was
intentional, convert the expression to 'unknown' first.ts(2352)

有没有一种方法可以通过类型和泛型来实现?

1 个答案:

答案 0 :(得分:1)

此类函数很难在TypeScript中进行编码。使用以下替代版本要简单得多,而且更简洁,只需输入一个“聚合”输入参数,在您的示例中为{ list: { a: 'aa' }}

function createRequestBis<K extends string, T>(payload: { [k in K]: T }) {
    const operation = Object.keys(payload)[0] as K;
    return Object.assign({
        operationVersion: 1,
        protocolVersion: 2,
        operation,
    }, payload);
}

const listRequest = createRequestBis({ list: { a: 'aa' } });
listRequest; // Type { operationVersion: number... } & { list: { a: string } } -> True but ugly!
listRequest.operation; // Type "list" -> OK
listRequest.list.a;    // Type "string" -> OK

它可以工作,但是推断的返回类型有点难看。我们可以使用自定义实用程序类型来对其进行增强,该实用程序类型可以解构+重构对象类型:

type Prettify<T> = T extends infer Tb ? { [K in keyof Tb]: Tb[K] } : never;

const header = {
    operationVersion: 1,
    protocolVersion: 2
};

function createRequestPretty<K extends string, T>(payload: { [k in K]: T }) {
    const operation = Object.keys(payload)[0] as K;
    const result = Object.assign({ operation }, header, payload);
    return result as any as Prettify<typeof result>;
}

const listRequest2 = createRequestPretty({ list: { a: 'aa' } });
listRequest2; // Type { operation: "list"; operationVersion: number; protocolVersion: number; list: { a: string } } -> OK