【问题标题】:Type guard expands generic to all possible values?类型保护将泛型扩展到所有可能的值?
【发布时间】:2019-08-30 09:14:12
【问题描述】:

这是一个简化的代码 sn-p 来重现我的问题:

type Supported = 'foo'|'bar';
type State = {
  [K in Supported]?: K[]
}
function test<T extends keyof State>(state: State, type: T) {
  const arr = state[type];
  if (!arr) {
    return;
  }
  return arr[0];
}
function test2<T extends keyof State>(state: State, type: T) {
  const arr = state[type] as T[]|undefined;
  if (!arr) {
    return;
  } 
  return arr[0];
}

在第一个函数中,arr 的类型是 if 块之前的 State[T]。它变成了 "foo"[] | “酒吧”[] 之后。 在test2中,我手动将arr转换为状态的实际值类型,返回值类型正确。

在类型保护过滤掉未定义后,泛型类型 T 似乎丢失了。这是预期的行为吗?

http://www.typescriptlang.org/play/#code/C4TwDgpgBAygrmMB7ATsCATKBeKByAMySTwB88AjAQxTwG4AoUSWYK9HKAbwaigG0A0lACWAO1gJkaTAF0A-AC4og-rIYBfBgThiAxsBFIJ6AM7AAPABUoEAB7oxGU1ADWEEEgKt2EAHwAFOa+yjBs6AA0UMwQylYAlNy8UHrG5lA0KJzB6Pwxsox8It4BAISZiTx8fCgQwHAoYoVQWjV1DRKZ-AAMBVAA9P0pacAZKCjKAERESJNqUKRQk9Qoc+paOvqGxtEQ5gBM1rYOEE4u7p7eYb6BObE+kdHg9wlJfKli6ZnZ4RB5z7IMi4rGpSLoMBACOJMM1ilAyhU3tVavVGs0NFBkiiOmMUD0+oNhp9Rpk4mpNAwgA

【问题讨论】:

    标签: typescript generics


    【解决方案1】:

    我认为这里发生的事情类似于this reported issuethis Stack Overflow question...当您从constrained generic 读取属性时(例如,State[K] 其中K extends keyof State),泛型类型被扩大到它的约束(所以K 变为keyof State,而State[K] 被评估为State[keyof State],即"foo"[] | "bar"[] | undefined。在您的情况下这并不完全错误,@ 987654334@ 是 "foo"[] | "bar"[] | undefined 类型...它只是不像您想要的那样具体

    您希望看到state[type] 类似于K[] | undefined。但是编译器不会为您执行此操作……它需要一些编译器不知道如何执行的高阶类型分析。它必须能够计算出Exclude&lt;State[K], undefined&gt;[0] 等价于K,但它不能。

    您在test2 中使用的类型断言对我来说似乎是一个合理的解决方法。

    另一种可能性是将state 参数的类型从State 扩大到当您对其进行索引时它被强制视为K[] | undefined 的东西。例如,Partial&lt;Record&lt;K, K[]&gt;&gt; 之类的东西。直接使用它的唯一问题是编译器将同时使用typestate 参数来推断K,这可能会将K 扩大到完整的keyof State。我们只想用type来推断K,所以最好告诉编译器使用statein a "non-inferential" way中的类型参数。一种方法,according to a language maintainer,是在我们不希望推理发生的地方用K &amp; {} 替换K。这导致我们这样做:

    function test3<K extends keyof State>(
      state: Partial<Record<K & {}, K[]>>,
      type: K
    ) {
      const arr = state[type]; 
      // const arr: { [P in K]?: P[] | undefined; }[K]
      if (!arr) {
        return;
      }
      // const arr: K[]
      return arr[0];
    
    }
    

    现在编译器知道在消除undefined 之后,arr 将是K[] 类型。因此test3 的返回类型被推断为K。万岁!是的,与你的类型断言相比,这需要很多麻烦,所以在实践中我可能只是断言并继续前进。

    希望有所帮助;祝你好运!

    Link to code

    【讨论】:

    • 这很棘手,但很有意义。感谢您的彻底回复!
    【解决方案2】:

    实际上您的第一个示例推断正确,第二个示例中的类型转换和数组类型有点不准确。在您的具体情况下,这并不重要,因为您只返回第一个元素。先来看看test

    test函数

    State[T]"foo"[] | "bar"[] | undefined 相同。你也可以这样写:

    State[T] -> State["foo" | "bar"] -> State["foo"] | State["bar"] -> "foo"[] | "bar"[] 
    
    => "foo"[] | "bar"[] | undefined (optional properties possible)
    

    所以arr 在你的函数末尾有"foo"[] | "bar"[] 类型是正确的,arr[0] 类型"foo" | "bar",因为你的 if 块排除了未定义的值。 IntelliSense 显示的类型表示可能有点令人困惑,因为有时它们最终会更冗长/细化,有时更紧凑/无法解析。编译器的规范类型是相同的。

    test2比较

    一开始,我说过您的强制转换数组类型有点不准确。假设我们返回 test1test2 中的整个数组(不仅是第一个元素),以说明问题。

    testtest2 的新函数签名

    // test signature
    <T extends Supported>(state: State, type: T): State[T]
    // test2 signature
    <T extends Supported>(state: State, type: T): T[] | undefined
    

    测试用例:

    // define some variables
    declare const state: State;
    declare const stateType: "foo" | "bar"; 
    
    // invoke functions
    test(state, stateType); // return type: "foo"[] | "bar"[] | undefined
    test2(state, stateType); // return type: Supported[] | undefined
    

    结果:

    const test_sample1: "foo"[] | "bar"[] | undefined = ["foo", "foo"] // works
    const test_sample2: "foo"[] | "bar"[] | undefined = ["foo", "bar"] // <-- error!
    const test2_sample1: Supported[] | undefined = ["foo", "bar"] // works
    const test2_sample2: Supported[] | undefined = ["foo", "foo"] // works
    

    因此,在test2 中手动转换时,您可以返回["foo", "bar"],而test 则不可能。部分原因是,以下不一样:

    "foo"[] | "bar"[] !== ("foo"|"bar")[]
    

    Playground

    希望,它会有所帮助。干杯

    【讨论】:

    • 感谢您的评论!我只是考虑将字符串文字 'foo' 或 'bar' 传递给测试函数。如果传入 'foo'|'bar' 类型的变量,则会发生您所描述的问题并且转换可能不准确。
    猜你喜欢
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-19
    • 1970-01-01
    相关资源
    最近更新 更多