【问题标题】:Narrowing a type based on a string literal in typescript?根据打字稿中的字符串文字缩小类型?
【发布时间】:2020-06-12 07:12:19
【问题描述】:

我想键入一个 api 响应,其中资源在资源名称键上返回,即{ [resourceName]: resources, total: 10 }。在以下示例中,我将如何键入响应对象来表示响应中存在applesoranges,具体取决于请求的内容:

interface Response {
    total: number
    apples: Array<object>
    oranges: Array<object>
}

const get = async (fruit: 'apples' | 'oranges') => {
    const data: Response = await fetchFruit(`https://example.com/${fruit}`)
    const count = data.total
    const applesOrOranges = data[fruit].filter(Boolean)
}

认为我必须为不同的可能响应使用联合类型,但我不清楚如何根据传递给函数的字符串文字来缩小联合类型:

interface Response {
    total: number
}
interface ApplesResponse extends Response {
    apples: Array<object>
}
interface OrangesResponse extends Response {
    oranges: Array<object>
}
type FruitResponse = ApplesResponse | OrangesResponse

const get = async (fruit: 'apples' | 'oranges') => {
    const data: FruitResponse = await fetchFruit(`https://example.com/${fruit}`)
    const count = data.total
    const applesOrOranges = data[fruit].filter(Boolean)
}

【问题讨论】:

    标签: typescript


    【解决方案1】:

    首先,响应准备错误,端点应该有一个单一的结构,如果您的端点返回applesoranges,那么BE dev建模响应的正确示例是:

    type Response = {
        results: Array<object>
        resultType: 'apple' | 'orange'
    }
    

    或者每个对象都应该有自己的类型,例如:

    type Fruit = {
      type: 'apple' | 'orange',
      ... other data
    }
    type Response = {
        results: Array<Fruit>
    }
    

    BTW - total 属性也没有意义,可以查看返回数组的长度。

    甚至响应可能只是Array&lt;Fruit&gt;。如果我们有苹果或橙子,我们真的不需要不同的钥匙


    很抱歉一开始的抱怨:)。如果响应不会改变,现在如何在 FE 级别处理您的问题。我会以不同的方式对fetchFruit 的类型进行建模,以说明结果具有由参数提供的键。考虑这样的函数声明:

    type FruitTypes = 'apples' | 'oranges';
    // below declaration, you need to provide the implementation
    declare function fetchFruit<F extends FruitTypes>(fruit: F): Promise<FruitResponse & Record<F, Array<object>>>;  
    
    const get = async (fruit: FruitTypes) => {
        const data = await fetchFruit(fruit)
        const count = data.total
        const applesOrOranges = data[fruit].filter(Boolean)
    }
    

    如果我们说fetchFruit 需要一个FruitType(你需要在里面放置url),那么我们可以将return 定义为FruitResponse &amp; Record&lt;F, Array&lt;object&gt;&gt; 这意味着它将返回肯定提供密钥的对象,所以如果你提供apples 会有apples 键。好的,希望对您有所帮助。

    【讨论】:

      【解决方案2】:

      您可以将getfruit 参数设为泛型(K)并解析依赖于K 的返回类型:

      type FruitResponse = ApplesResponse | OrangesResponse
      type FruitResponseKeys = 'apples' | 'oranges' // or extract it from the response types
      type FruitResponseByKey<K extends FruitResponseKeys> = Extract<FruitResponse,{ [P in K]: any }>
      
      const get = async <K extends FruitResponseKeys>(fruit: K): Promise<FruitResponseByKey<K>> => {
          // add validation logic for fetched data if you need that
          // we cast it here as any for brevity
          const data: FruitResponseByKey<K> = await fetch(`https://example.com/${fruit}`) as any
          const applesOrOranges = data[fruit].filter(Boolean)
          return { ...data, [fruit]: applesOrOranges }
      }
      
      const apples = get("apples") // Promise<ApplesResponse>
      const oranges = get("oranges") // Promise<OrangesResponse>
      

      FruitResponseByKey extracts ApplesResponseOrangesResponse 来自 FruitResponse 基于键 K'apples' | 'oranges'

      我认为,最有用的部分是调用者根据fruit 键获得正确的水果数组。由于fetch 自然需要一个类型断言,我们在函数体中为了简单起见只使用anyget 中的逻辑也没有任何复杂性,所以这应该很合适。

      Code sample

      【讨论】:

        猜你喜欢
        • 2018-04-28
        • 1970-01-01
        • 2019-03-24
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        • 2023-02-03
        • 2017-08-21
        • 2020-12-22
        相关资源
        最近更新 更多