我最近才開始在 Typescript 中使用高級型別和泛型,所以如果我的表述不完全正確,請原諒 - 至少我希望標題有意義。
更新-> 我在這里添加了一個完整的代碼示例
我想做的事
假設有兩個(查詢)函式,其中一個只接受一個物件作為引數,另一個接受一個查詢字串和一個物件作為引數。
// only object
const query1 = ({ ...params }) => { return ...; }
// query AND object
const query2 = (query, { ...params }) => { return ...; }
這兩個函式都包含在單獨的類中,它們也被輸入
class Q1 {
private query1: Query1;
...
}
class Q2 {
private query2: Query2;
...
}
型別定義
基本上,我想要實作的是兩種函式型別都源自單個泛型型別。到目前為止我所擁有的是
// the generic object type --> I guess here I would need to define it differently
type Param<T> = { [key in keyof T]: T[key] }
// the generic query type which contains `params` and `response` types
type QueryType<P = any, R = any> = {
params: Param<P>
response: Promise<R>
}
// the generic function type for my queries
type Query<P = any, R = any> =
<T extends QueryType<P,R>>( params: Pick<T, 'params'>['params'] ) => Pick<T, 'response'>['response']
For the first case query1() I do not really need to exend anything. I only need to apply the param and response types, eg
interface Input {
param1: string,
param2: number,
param3: boolean
}
type QuerySomething = QueryType<Input, string>
class Q1 {
private query1: Query;
// just for completeness - I do not wanna type out `query1` here
constructor( query1: Query ) {
this.query1 = query1;
}
// and I would use this somehow like
public async querySomething(input: Input): Promise<string> {
const res = await this.query1<QuerySomething>( input );
return res;
}
}
For the second case I simply can't figure out how to extend type Query that it will accept a parameter query: string in addition to the params object, without rewriting type Query. So
interface Input {
param1: string,
param2: number,
param3: boolean
}
// I guess I have to do something here?
type QuerySomething = QueryType<Input, string>
class Q2 {
private query2: Query;
...
public async querySomething(query: string, params: Input): Promise<string> {
const res = await this.query2<QuerySomething>( query, params );
return res;
}
}
What I could do of course
type Query2<P = any, R = any> =
<T extends QueryType<P,R>>( query:string, params: Pick<T, 'params'>['params'] ) => Pick<T, 'response'>['response']
I hope this makes sense! I am still really confused with Typescript and I apologise if this is complete nonsense here!
Thanks!
uj5u.com熱心網友回復:
可能有幫助的一件事是退后一步并嘗試描述兩個Q類都應該實作的基本抽象類,您可能會想出這樣的東西:
abstract class Q_Base {
public abstract querySomething(...args: any[]): Promise<any>
}
然而,我們顯然希望引數型別和回傳型別受到約束,我們可能會考慮采用一個泛型,它只是該方法的整個呼叫簽名:
abstract class Q_Base2<Query_signature extends (...args:any[])=>Promise<any>> {
public abstract querySomething(...args:Parameters<Query_signature>): ReturnType<Query_signature>;
}
interface Q1Input {
// I assume this is well defined for you
}
class Q1 extends Q_Base2<(input: Q1Input)=>Promise<string>>{
public async querySomething(input: Q1Input): Promise<string>{
return ""
}
}
由于此型別必須使用單個泛型進行解包Parameters并ReturnType為函式型別使用單個泛型有點傻(如果您使用的是箭頭函式,您可以使用,public abstract querySomething: Query_Signature但這取決于您),因此也許我們可以使用 2 個泛型來表示引數并分別回傳型別:
abstract class Q_Base3<Params extends any[], R extends Promise<any>> {
// could also leave no retriction on R and return type Promise<R> depending on which semantics are easier for you
public abstract querySomething(...args: Params): R
}
class Q1_3 extends Q_Base3<[input: Q1Input], Promise<string>>{
// this was filled in with the quick fix of Q1_3 does not implement necessary abstract methods
public querySomething(input: Q1Input): Promise<string> {
throw new Error("Method not implemented.");
}
}
其中任何一個都可能對您有用,但我認為這里的根本幫助是這樣的想法,即對于多型性,您可以定義多個實作符合的一種型別,即使使用泛型也有某種意義Q1并且Q2具有相似的行為,您可以嘗試在基類中捕獲它,無論您是否實際使用該基類。(分別在 Q1 和 Q2 上定義泛型同樣有效)
uj5u.com熱心網友回復:
在過去幾天嘗試了多種不同的方法后,我為我的問題找到了一個(某種)令人滿意的解決方案。我不想感謝@Tadhg McDonald-Jensen花時間把我推向正確的方向!
所以,我改變并簡化了泛型型別定義
// the generic object type
type Param<T> = { [key in keyof T]: T[key] }
// the generic query type
type QueryType<P, R> = {
params: Param<P>
response: Promise<R>
}
// generic typecast
type TypeCast<T> = Param<T>[keyof T]
// the generic query type
type Query<P = any, R = any> =
<T extends QueryType<P, R>>( ...args: TypeCast<T['params']>[] ) => T['response']
雖然 typeParam和QueryType保持不變Query已更改為接受多個引數,但我添加了一個額外的 type TypeCast,它只回傳引數的型別。所以如果我們回到最初的例子,我們會得到
// the input parameters
interface Input {
param1: string
param2: number
param3: boolean
}
// the type which we want to apply to the query function
type QuerySomething = QueryType<{ query: string, input: Input }, string>
// testing
type test = TypeCast<QuerySomething['params']>
// resolves to type test = string | Input
將此應用于查詢功能將給出所需的結果
我可能應該提一下,我最初的想法是不要querySomething在每個類中使用相同的類方法名稱。這些方法只是代表了許多不同類方法的超集。這兩個類之間的區別已經實作,query1代表來自一個外部 API 端點和query2另一個 API 端點的 fetch 方法。
type QuerySomething = QueryType<{ input: Input }, string>
type QuerySomethingElse = QueryType<{ query: string, input: Input }, string>
class Q {
// just for simplification, these methods would be in different classes
private query1: Query;
private query2: Query;
constructor( query1: Query ) {
this.query1 = query1;
this.query2 = query2;
}
public async querySomething( query: string, input: Input ): Promise<string> {
const res = await this.query1<QuerySomething>( input );
return res;
}
public async querySomethingElse( query: string, input: Input ): Promise<string> {
const res = await this.query2<QuerySomethingElse>( query, input );
return res;
}
}
這將解決
// Typechecks
const res = await this.query1<QuerySomething>( input ); // OK
const res = await this.query1<QuerySomething>( query ); // ERROR -> OK
const res = await this.query2<QuerySomethingElse>( query, input ); // OK
const res = await this.query2<QuerySomethingElse>( input, query ); // ERROR -> OK
// Argument checks
const res = await this.query1<QuerySomething>(); // OK -> should be ERROR
const res = await this.query1<QuerySomething>( input, input ); // OK -> should be ERROR
const res = await this.query2<QuerySomethingElse>( query, input, query ); // OK -> should be ERROR
const res = await this.query2<QuerySomethingElse>( query, input, input ); // OK -> should be ERROR
So basically, it does the right thing but by type definition does not take care about the number of arguments. If any of the Typescript wizards knows how to do this, I'd be happy to hear their solution! For now I think this is the best I can do.
If anyone is interested, I have updated the TS Playground.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/356477.html
