【问题标题】:Generics in typescript - not assignable to type / parameter of type打字稿中的泛型 - 不可分配给类型/类型的参数
【发布时间】:2021-11-23 17:29:38
【问题描述】:

我有以下两个服务调用,它们都使用方法 ConcertDates()。

private callMyFirstService(params: MyParams): Observable<MyDto> {
    return this.http.post<MyDto>(params).pipe(map(response => this.convertDates<MyDto>(response)));
}

private callMySecondService(params: MyOtherParams): Observable<MyAnotherDto> {
    return this.http.post<MyAnotherDto>(params).pipe(map(response => this.convertDates<MyAnotherDto>(response)));
}

由于这些方法与另一种类型的 dto 一起运行,因此我尝试实现一些在这两种情况下都可以调用的通用函数。 我有类似的东西:

private convertDates<type>(input: type): type {
    for (const key in input) {
        if (!input.hasOwnProperty(key)) {
            continue;
        }

        if (typeof input[key] === 'object') {
            this.convertDates(input[key]);
        } else if (typeof input[key] === 'string' && this.MY_REGEXP.test(input[key])) {
            input[key] = new Date(input[key]);
        }
    }
    return input;
}

这似乎不起作用,因为我总是得到:

Type 'Date' is not assignable to type 'type[Extract<keyof type, string>]'

还有……

Argument of type 'type[Extract<keyof type, string>]' is not assignable to parameter of type 'string'.
  Type 'type[string]' is not assignable to type 'string'.

有什么想法吗? 提前非常感谢!

【问题讨论】:

    标签: javascript typescript templates typescript-typings typescript-generics


    【解决方案1】:

    type 的任何类型都有特定的形状。您似乎期待一个具有某些字符串属性的对象,而您将其 更改Date。这意味着您违反了该类型。

    或者更简单地说:

    const obj = { maybeDate: 'foo' } // type is { maybeDate: string }
    obj.maybeDate = new Date() // error
    

    maybeDate 的类型是string,因此您不能只将Date 分配给该插槽。这违反了类型。


    通常在进行这样的转换时,您需要将对象转换为中间类型,然后将其转换为最终类型。

    const obj = { maybeDate: 'foo' } // type is { maybeDate: string }
    const objIntermediate = obj as { maybeDate: string | Date }
    objIntermediate.maybeDate = new Date()
    const objFinal = objIntermediate as { maybeDate: Date }
    

    Playground


    在这一点上,重要的是要说您的逻辑无法输入。首先,因为 typescript 不支持正则表达式转换,但更重要的是类型系统不知道是否应该将 string 转换为日期,因为它不会提前知道该字符串的内容。

    interface MyDto {
      name: string
      createdAt: string // How could typescript possible know this is a date string?
    }
    

    我在自己的代码库中以不同的方式解决了问题。我输入Dto 使字段为Date,例如:

    interface MyDto {
      name: string
      createdAt: Date
    }
    

    然后使用一些自定义 JSON 解码器逻辑来更改字段:

    /** Parse JSON content from a response. If any string matches the ISO date time format, convert it to a Date. */
    async function decodeJson(res: Response): Promise<any> {
      return JSON.parse(await res.text(), (key: string, value: unknown) => {
        if (typeof value === 'string' && dateFormat.test(value)) {
          return new Date(value)
        }
        return value
      })
    }
    const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/
    

    调用者:

    function fetchStuff<T>(path: string) {
      const res = await window.fetch(path)
      const json = await decodeJson(res)
      return json as T
    }
    
    // Then in some async function
    const myStuff = await fetchStuff<MyDto>('/api/foo')
    myStuff.createdAt.getHours() // works
    

    这是有效的,因为响应是any,直到它被转换为泛型类型,所以打字稿让你用它做任何事情。 (老实说,我不确定如何使其适应 rxjs 驱动的代码)

    Playground

    这种自动转换日期的整个方法的最大缺点是,如果有人在某些文本字段中手动键入日期之类的字符串,它将被转换为日期,但您的 Dto 会将其键入为字符串,并且这可能会使您的应用程序崩溃。例如,有人输入他们的名字为2015-01-02。在我的应用程序中,这是非常不可能的,值得方便。但这对你来说可能无法忍受。


    作为最后的附注,如果 JSON 有一个原生的 Date 类型,本可以省去的麻烦无疑是无数的......

    【讨论】:

    • 非常感谢您的详细解答!
    猜你喜欢
    • 2020-03-09
    • 2019-08-11
    • 2021-10-11
    • 2021-03-02
    • 2020-06-29
    • 1970-01-01
    • 2016-09-26
    • 2021-07-23
    • 1970-01-01
    相关资源
    最近更新 更多