【问题标题】:Typescript workaround to return narrowed typesTypescript 解决方法以返回缩小的类型
【发布时间】:2018-06-19 02:54:29
【问题描述】:

在 typecipt 中,我需要一种方法让函数在给定特定类型的参数的情况下返回与参数类型相关的类型的对象。

请参阅下面的示例。我需要一种方法来使 'const response = ...' 的类型更窄。

以下示例用于将特定类型的请求链接到仅与给定请求相关的响应。例如,给定一个查找用户信息的请求,我们希望得到一个包含他们的姓名和年龄的响应。但是,当收到查找汽车信息的请求时,我们希望得到有关汽车品牌和里程信息的响应。我们只想对“用户”请求使用“用户”响应,对“汽车”使用类似的响应。

class RequestBase {
}

class ResponseBase {
}

interface IFindUserReq {
    user_id :string
}
class FindUserRequest implements IFindUserReq {
    user_id :string
    constructor(user_id) {
        this.user_id = user_id
    }
}

interface IFindUserRes {
    name :string
    age  :number
}
class FindUserResponse implements IFindUserRes {
    name :string
    age  :number
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

interface IFindCarReq {
    car_id :number
}
class FindCarRequest implements IFindCarReq {
    car_id :number 
    constructor(car_id) {
        this.car_id = car_id
    }
}

interface IFindCarRes {
    make :string
    miles :number
}
class FindCarResponse implements IFindCarRes {
    make :string
    miles  :number
    constructor(make, miles) {
        this.make = make;
        this.miles = miles;
    }
}

const request = new FindUserRequest("foo")
const response = performRequest(request) // the type here is 'RequestBase | undefined'. Is there any way to automatically narrow it to be FindCarResponse?

function performRequest(req :RequestBase) : RequestBase | undefined {
    if (req instanceof FindUserRequest) {
        return new FindUserResponse("foo", 23) // hard coded example for convenience
    } else if (req instanceof FindCarRequest) {
        return new FindCarResponse("toyota", 10000)
    }
}

更新:解决方案 1 灵感来自Variable return types based on string literal type argument

一种方法是像这样重载“performRequest”签名:

function performRequest(req :FindCarRequest) : FindCarResponse 
function performRequest(req :FindUserRequest) : FindUserResponse
function performRequest(req :RequestBase) : ResponseBase | undefined   {
    if (req instanceof FindUserRequest) {
        return new FindUserResponse("foo", 23) // hard coded example for convenience
    } else if (req instanceof FindCarRequest) {
        return new FindCarResponse("toyota", 10000)
    }
}

但是,我真的希望维护请求和响应类型的库不必更改使用请求和响应类型 (performRequest) 的应用程序中的函数签名。所以我还是想听听其他解决方案。

更新解决方案 2 感谢来自 TS Gitter 频道的 Gerrit Birkeland:

class RequestBase {
    _responseType : ResponseBase
}

class ResponseBase {
}

interface IFindUserReq {
    user_id :string
}
class FindUserRequest extends RequestBase implements IFindUserReq {
    _responseType :FindUserResponse
    user_id :string
    constructor(user_id) {
        super()
        this.user_id = user_id
    }
}

interface IFindUserRes {
    name :string
    age  :number
}
class FindUserResponse extends ResponseBase implements IFindUserRes {
    name :string
    age  :number
    constructor(name, age) {
        super()
        this.name = name;
        this.age = age;
    }
}

interface IFindCarReq {
    car_id :number
}
class FindCarRequest extends RequestBase implements IFindCarReq {
    _responseType :FindCarResponse
    car_id :number 
    constructor(car_id) {
        super()
        this.car_id = car_id
    }
}

interface IFindCarRes {
    make :string
    miles :number
}
class FindCarResponse extends ResponseBase implements IFindCarRes {
    make :string
    miles  :number
    constructor(make, miles) {
        super()
        this.make = make;
        this.miles = miles;
    }
}

const request = new FindUserRequest("foo")
const response = performRequest<FindUserRequest>(request) // the type of response here is ResponseBase, not sure why it's not narrowed 

function performRequest< T extends RequestBase>(req :T) :T["_responseType"]    {

    if (req instanceof FindUserRequest) {
        return new FindUserResponse("foo", 23) // hard coded example for convenience
    } else if (req instanceof FindCarRequest) {
        return new FindCarResponse("toyota", 10000)
    } else {
        return new ResponseBase()
    }
}

【问题讨论】:

    标签: typescript


    【解决方案1】:

    如果您可以将RequestBase 更改为泛型,则可以添加表示响应类型的泛型类型参数。然后,您可以使用它来返回响应类型。

    class RequestBase<TResponse> {
        performRequest() : TResponse {
            return null; // Dummy, could invoke the actual performRequest
        }
    }
    class FindCarRequest extends RequestBase<FindCarResponse> {
        constructor(public car_id: number) {
            super();
        }
    }
    class FindCarResponse {
        constructor(public make: string, public miles: number) {
        }
    }
    
    const request = new FindCarRequest(100)
    const response = request.performRequest() // Will be typed as FindCarResponse
    

    注意不幸的是,将performRequest 函数保留为外部函数不是一种选择,因为类型推断不够聪明,无法从基类型中获取泛型类型参数:

    function performRequest<T>(req: RequestBase<T>):T {
        return null; // Dummy
    }
    
    const request = new FindCarRequest(100)
    const response = performRequest(request) // Will be typed as {} although I would have expected this to be FindCarResponse
    
    const request2 = new RequestBase<FindCarResponse>()
    const response2 = performRequest(request) // Will be typed as FindCarResponse
    

    【讨论】:

    • 感谢您将这些放在一起 - 问题是我们希望在返回类型中有一个特定的接口,以链接到请求类型的特定接口。虽然可以使特定接口成为通用接口,但似乎没有办法使用您建议的方法从另一个接口获取一个特定接口。
    【解决方案2】:

    您可以(大部分)通过向RequestBase 类添加属性来实现所需的效果。这个属性不需要用于任何事情,但它确实需要存在。

    (从我的 gitter 消息中复制)

    class RequestBase {
      _responseType: ResponseBase
    }
    
    class ResponseBase {}
    
    class FindUserRequest implements RequestBase {
      _responseType: FindUserResponse
      user_id: string
      constructor(user_id: string) {
        this.user_id = user_id
      }
    }
    
    class FindUserResponse {
      name: string
      age: number
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    }
    
    const request = new FindUserRequest("foo")
    const response = performRequest(request) // FindUserResponse | undefined
    
    function performRequest<T extends RequestBase>(req: T): T["_responseType"] | undefined {
      if (req instanceof FindUserRequest) {
        return new FindUserResponse("foo", 23)
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-27
      • 2021-04-05
      • 2023-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多