繁体   English   中英

typescript 可以根据参数值推断函数的响应类型吗?

[英]Can typescript infer a function's response type based on parameter values?

我有一个 API 请求 object 以获取记录列表。 如果在请求inlineCount上设置了 inlineCount,则 API 返回{ results: T[], inlineCount: number } 否则,返回T[]

这看起来像:

interface inlineCountResult<T> {
    results: T[];
    inlineCount: number;
}

interface Options {
  inlineCount?: boolean;
}

async getApiResponse<T>(options: Options): T[] | inlineCountResult<T> {
  return this.http.get(options);
}

async getClients(options: Options) {
  return this.getApiResponse<model.Client>(options);
}

这是我希望发生的事情:

let responseAsArray: model.Client[] = getClients();
let responseWithCount: inlineCountResult<model.Client> = getClients({ inlineCount: true });

但是,这两个调用的类型都是model.Client[] | inlineCountResult<model.Client> model.Client[] | inlineCountResult<model.Client>这使我的代码需要不必要的类型转换才能正常工作。 我知道我可以执行以下操作,但我想避免as强制转换:

const clients: Client[] = await (getClients(clientQueryOptions) as Promise<Client[]>);

我的一个想法是编写一个 function 它将返回一种返回类型作为参数之一,但我想在不需要额外参数的情况下过关。

部分解决方案:

//
// 2 "normalize" functions each with a single return type
//
export function normalizeArray<T>(result: T[] | inlineCountResult<T>): T[] {
    return Array.isArray(result) ? result : result.results;
}

export function normalizeInlineCount = function normalizeInlineCount<T>(result: T[] | inlineCountResult<T>) {
    return Array.isArray(result) ? new inlineCountResult<T>(result, result.length) : result.inlineCount ? result : new inlineCountResult<T>([], 0);
}

//
// Types for request
//
export interface ExecuteQueryRequest {
    inlineCount?: boolean;
}
export type QueryResponseNormalizer<T, TResponse> = (response: (T[] | inlineCountResult<T>)) => TResponse;

//
// Query method to handle
//
function executeQuery<T, TResponse>(request: ExecuteQueryRequest, normalizeResults: QueryResponseNormalizer<T, TResponse>): TResponse {
    const items: T[] = []; // eg call HTTP service

    return normalizeResults(items);
}

// One API for both scenarios
function getClients<TResponse>(request: ExecuteQueryRequest, normalizeResults: QueryResponseNormalizer<model.Client, TResponse>) {
    let response = executeQuery(request as ExecuteQueryRequest, normalizeResults);
    return response;
}

//
// Example calls
//
let responseA: inlineCountResult<model.Client> = getClients({ inlineCount: true }, normalizeInlineCount);
let responseB: model.Client[] = getClients({}, normalizeArray);

对于条件类型,结果类型可能取决于参数中是否存在inlineCount ,并且还可以从规范化 function 推断结果数组的类型。 为简单起见,忽略承诺,您可以定义这些帮助器类型:

interface InlineCountResult<T> {
  results: T[]
  inlineCount: number
}

interface InlineCountOption {
  inlineCount: boolean
}

type PlainNormalizer<T> = (res: T[]) => T[]

type InlineCountNormalizer<T> = (res: InlineCountResult<T>) => InlineCountResult<T>

type Normalizer<O, T> =
  O extends InlineCountOption ? InlineCountNormalizer<T> : PlainNormalizer<T>

type Result<O, T> = O extends InlineCountOption ? InlineCountResult<T> : T[] 

并像这样输入getClients

function getClients<O, T>(options: O, normalizer: Normalizer<O, T>): Result<O, T> {
  return {} as any
}

如果您现在定义两个像这样的虚拟归一化器

const normalizeInlineCount: InlineCountNormalizer<number> = {} as any
const normalizeArray: PlainNormalizer<string> = {} as any

响应的类型将被正确推断,错误类型的规范器将产生类型错误:

const responseA = getClients({ inlineCount: true }, normalizeInlineCount)
const responseB = getClients({}, normalizeArray)
const responseC = getClients({ inlineCount: true }, normalizeArray) // type error
const responseD = getClients({}, normalizeInlineCount) // type error

TypeScript操场

您需要对其进行一些调整以考虑承诺,以及除inlineCount之外的其他选项属性,但这应该让您对解决方案有一个粗略的了解。

Oblosys 的回答帮助我找到了一个解决方案,该解决方案允许我的理想/简单 API 表面,而不需要我的调用者中的任何额外参数/类型,除了 2 个不同的OptionsInlineCountOptions类型有和没有inlineCount布尔:

TypeScript操场

// model.Client[]
const clients = await getClients();
// or
const clients2 = await getClients({ sort: "clientName" });

// InlineCountResult<model.Client>
const response = await getClients({ inlineCount: true });

完整的解决方案

设置

为来自 API 的数据定义实体

namespace model {
  export interface Client {
    clientName: string;
  }
}

定义请求/响应接口

interface Options {
  filter?: any;
  skip?: number;
  page?: number;
  sort?: string;
}

interface InlineCountOption extends Options {
  inlineCount: boolean
}

interface InlineCountResult<T> {
  results: T[]
  inlineCount: number
}

type Option<O extends Options> = O extends InlineCountOption ? InlineCountOption : Options;
function queryOptions<O>(options: O): Option<O> {
  return options as any;
}

方法

通用 API 调用者,例如fetch

type Result<O, T> = O extends InlineCountOption ? InlineCountResult<T> : T[] 
async function hitApi<O, T>(url: string, options: O): Promise<Result<O, T>> {
  let data: unknown = []; // ex

  return data as Result<O, T>;
}

特定于每个实体/模型

function getClients<O>(options: O): Promise<Result<O, model.Client>> {
  return hitApi('getClients', options);
}

用法

数组请求

const clientQueryOptions = queryOptions({});

// model.Client[]
const clients = await getClients(clientQueryOptions);

const clientNames = clients.map(x => x.clientName);

内联计数请求

const clientQueryOptions = queryOptions({ inlineCount: true });

// InlineCountResult<model.Client>
const clientsResponse = await getClients(clientQueryOptions);

const numClients = clientsResponse.inlineCount;
const clients = clientsResponse.results;
const clientNames = clients.map(x => x.clientName);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM