TypeScript要求提供泛型参数

yx2lnoni  于 2023-02-17  发布在  TypeScript
关注(0)|答案(6)|浏览(181)

我有以下功能:

async function get<U>(url: string): Promise<U> {
    return getUrl<u>(url);
}

但是,可以这样调用它(U被TS设置为any):

get('/user-url');

是否有一种方法可以定义此函数,使其要求显式提供U,如

get<User>('/user-url');
njthzxwz

njthzxwz1#

没有内置的支持,但是我们可以设计一个场景,在那里不传入类型参数将生成一个错误,使用默认的泛型类型参数和条件类型。也就是说,我们将给予U一个默认值void。如果默认值是U的实际值,然后我们将参数输入到函数中,作为不应该真正传入的参数,从而得到一个错误:

async function get<U = void>(url: string & (U extends void ? "You must provide a type parameter" : string)): Promise<U> {
    return null as any;
}

get('/user-url'); // Error Argument of type '"/user-url"' is not assignable to parameter of type '"You must provide a type parameter"'.

class User {}
get<User>('/user-url');

错误信息并不理想,但我认为它会传达信息。

编辑:有关在参数类型中使用类型参数的解决方案,请参见此处

yftpprvb

yftpprvb2#

可以使用多个类型参数:

function contractType<T = void, U extends T = T>(value: unknown): U {
    return value as U
}

const example1: string = contractType(17) // error
const example2: string = contractType("value") // error
const example3: string = contractType<string>("value") // ok

https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-288902999

3xiyfsfu

3xiyfsfu3#

function createCommand<P extends WhatEver = never>(a: P): P {
    a.whatEverProperty; // a is always instanceof WhatEver. Never is overlooked here.
}

大多数情况下,最简单的解决方案是将never指定为默认类型参数值。never总是可赋值给类型参数,即使您使用WhatEver扩展它,因此函数体不受影响,但此处的返回值将是never,影响使用者以任何有意义的方式使用此函数的能力。
(使用者/调用者在尝试使用返回值时会得到一个错误,但如果您的函数纯粹是为了副作用而调用的,您可能需要考虑@Titian的回答中概述的方法)
当通常不返回P时,可以使用条件类型使返回类型为never

function createCommand<P extends WhatEver = never>(a: P): P['whatEverProperty'] {
    // a is always instanceof WhatEver. Never is overlooked here.
    return a.whatEverProperty as P extends WhatEver ?
      ? WhatEver
      : never
}
oxcyiej7

oxcyiej74#

还有一种方法!需要一个只用于类型推断的参数:

/**
 * getFoo gives you a well-typed Foo component
 * ```tsx
 * const foo = getFoo(config)
 * ```
 */
const getFoo = <Keys extends string>(
  _config: Record<Keys, unknown>
) => {
  return FooUntyped as FooType<Keys>
}

如果您有对象可随时用于传入此类型的推理,并且您的函数不接受参数,则这可能是一个很好的方法**从TS v4.1.2起 *

如果你没有现成的配置对象,调用它有点“wtf”,但你会直接在调用它的地方得到一个错误,而不是在更远的地方:

const foo = getFoo({} as Record<'my' | 'keys', unknown>)

这种设计/方法碰巧在另一个地方修复了我设计中的一个小问题。我实际上在我的getFoo上添加了一个额外的参数,称为defaultProps,这是给定Foo组件的一些默认属性(Foo是一个表单字段组件,配置键是有效的字段名)

brvekthn

brvekthn5#

这里的其他答案提供了一些不错的变通方法,可以让代码按照您想要的方式工作,但没有一个解决了根本问题,即您试图进行不安全的强制转换以使类型检查程序满意。
我没有getUrl<T>函数的源代码,但我强烈怀疑它看起来很像

function getUrl<T>(url: string): Promise<T> {
  return fetch(url).then(resp => resp.json());
}

在这个场景中,Response#json()返回Promise<any>,您正在悄悄地将其强制转换为Promise<T>--这是不安全的。当您编写const user: User = await get("./user");时,类型检查器很乐意推断您想要返回一个Promise<User>并为您填充它。即使您提供了默认值,它也会这样做。
一个更安全的方法是让getUrl返回unknown,这将迫使你在运行时用类型保护来检查你的返回值,或者至少显式地强制转换它((await get("./user")) as User等),在这种情况下,你的linter会因为不安全的强制转换而惩罚你。

u4dcyp6a

u4dcyp6a6#

由于下面的Ramda pathOr类型定义,我偶然发现了您的问题:

export function pathOr<T>(defaultValue: T, path: Path, obj: any): T;

该函数可以沿着路径查找对象内部深处的值。
问题是,如果你不提供泛型类型参数T,Typescript将使用defaultValue中的类型,当对象的内容在该路径中不具有相同的类型时,这是没有意义的。
这个解决方案很复杂,只有在有两个论点的情况下才有效。我不认为有一个简单的答案可以回答你的问题,适用于所有情况。

import {toPath} from 'lodash-es'
import {pathOr} from 'ramda'

// Aternative to lodash `get` where you have to provide a default value and a return type that includes the default value.
// Useful to avoid cases where the return type is inferred from the default value's type but does not fit with the object's content at the path.
export const getOr = <T = never, D extends T = T>(object: unknown, pathStr: string, defaultValue: D): T =>
  pathOr(defaultValue, toPath(pathStr), object)

公平地说,ramda的path函数和lodash的get函数都非常努力地根据对象和路径找到正确的返回类型,但我看不出同时使用T类型作为默认值和返回值是个好主意。

相关问题