0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Typescriptでassociatedtypeぽいことをする?

Posted at

はじめに

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?