はじめに
Swiftにあるassociatedtype的なやつがTypescriptでどうやるのか気になっていました。
※associatedtypeにについては以下の記事が非常に分かりやすかったので、リンクさせていただきました
https://qiita.com/akeome/items/78e650f27a4c53e1406a
例えば、以下のようにMyRequestのprotocolではResponseに対してMyResponseに準拠していることの制約だけかけて、準拠する側(GetUserRequestなど)で具体的な型を指定するやつです。
protocol MyResponse {}
protocol MyRequest {
associatedtype Response: MyResponse
func req() -> Response
}
// GetUser
class GetUserResponse: MyResponse {
let userName: String = "UserName"
}
class GetUserRequest: MyRequest {
typealias Response = GetUserResponse
func req() -> GetUserResponse {
return GetUserResponse()
}
}
// GetProject
class GetProjectResponse: MyResponse {
let projectName: String = "ProjectName"
}
class GetProjectRequest: MyRequest {
typealias Response = GetProjectResponse
func req() -> GetProjectResponse {
return GetProjectResponse()
}
}
class Client {
func request<T : MyRequest>(request: T) -> T.Response {
return request.req()
}
}
let cli = Client()
let res = cli.request(request: GetUserRequest());
print(res.userName)
let res2 = cli.request(request: GetProjectRequest());
print(res2.projectName)
やってみる
Typescriptには直接的にはassociatedtypeにあたるものはないようでした。
調べる中で「Indexed Access Types」と「ReturnType」という便利なやつを知り、この二つでそれっぽく出来ないか?と思いためしてみました。
Indexed Access Types
オブジェクトなどから特定のプロパティの型を抽出するやつ
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
> type Age = number
ReturnType
関数などから戻り値の型を抽出するやつ
type T0 = ReturnType<() => string>;
> type T0 = string
試す
interface MyResponse {
}
interface MyRequest {
req(): MyResponse
}
// GetUser
class GetUserResponse implements MyResponse {
userName: string = "UserName";
}
class GetUserRequest implements MyRequest {
req(): GetUserResponse {
return new GetUserResponse();
}
}
// GetProject
class GetProjectResponse implements MyResponse {
projectName: string = "ProjectName";
}
class GetProjectRequest implements MyRequest {
req(): GetProjectResponse {
return new GetProjectResponse();
}
}
class Client {
request<T extends MyRequest, U extends ReturnType<T["req"]>>(req: T) : U {
return req.req() as U;
}
}
const cli = new Client();
const res = cli.request(new GetUserRequest());
console.log(res.userName)
const res2 = cli.request(new GetProjectRequest());
console.log(res2.projectName)
肝は以下のところだけでしょうか。
request<T extends MyRequest, U extends ReturnType<T["req"]>>(req: T) : U {
return req.req() as U;
}
ReturnTypeの対象にMyRequestを実装したTの「req」メソッドを指定して、具象クラスで指定したResponseの型を取り出しています。
一応それぽくなったような、なっていないような微妙な気はしつつ本日は以上にしたいと思います。
さいごに
ZEROBILLBANKでは一緒に働く仲間を募集中です。
ZEROBILLBANK JAPAN Inc.
Youtubeチャンネルもやっています
https://www.youtube.com/channel/UCe947G2stpPUv0xz7lN_MAg