使用新的index as syntax,我可以过滤出如下类型的键.

// Filter out all keys of `T` that do not exist in `U`
type IntersectKeys<T, U> = {
    [K in keyof T as K extends keyof U ? K : never]: T[K];
};

// e.g., IntersectKeys<{ foo: string; bar: number}, { foo: never }> === { foo: string }

然而,当我将此类型用于泛型时,TypeScript似乎完全忽略了类型判断.

Is this a TypeScript bug? Is there a way around this?

interface FooBar {
    foo: string;
    bar: number;
}

function intersectFooBarKeys<U>(fb: FooBar, intersect: U): IntersectKeys<FooBar, U> {
    return false; // ---> !!Wrong implementation, but no type error!! <---
}

// Example usage
const fooBar: FooBar = {
    foo: 'foo',
    bar: 42,
};
const onlyFoo: { foo: string } = intersectKeys(fooBar, { foo: null });
// Based on its type, I would expect `onlyFoo` to be of the form `{ foo: string }`, but it is `false` instead!

推荐答案

这似乎是TypeScript中的一个bug或设计限制.编译器无法对高阶类型操作进行太多推理,例如key remappinggeneric类型.围绕通用密钥重新映射存在几个GitHub问题,如ms/TS#45212

type Foo<U> = {
  [K in "foo" | "bar" as Extract<K, keyof U>]: 1;
};

type X = Foo<{ foo: 0, baz: 0 }> // {foo: 1}
type Y = Foo<{ baz: 0, qux: 0 }> // {}
type Z = Foo<{ [k: string]: 0 }> // {foo: 1, bar: 1}

function generic<U extends { [k: string]: 0 }>() {
  type G = Foo<U> // {} <-- shouldn't this either be *deferred* or,
  // if anything, eagerly resolved to {foo: 1, bar: 1}? 
}

但我不知道会发生什么.


一般来说,当编译器看到涉及泛型类型参数的任何类型的复杂类型函数时,它可以决定对其进行defer求值,从而将其视为opaque类型—没有什么是可分配给它的...或者,它可以决定用与泛型类型参数相关的内容替换泛型类型参数来对其进行求值...从而可能将其解决为不正确的问题.看起来这里发生的是后者;不管U是什么,在重映射子句中,K extends keyof U ? K : never被过早地减少到never,因此得到IntersectKeys<FooBar, U>作为{},与U无关.这尤其令人遗憾,因为{}是特殊情况,不会引发额外的财产判断(见other answer),所以false以及除nullundefined之外的任何东西都可以分配给它.哎呀.

因此,我要说的是,这个问题类似于microsoft/TypeScript#24560年的问题.我们试图使用conditional types过滤与keyof泛型类型相关的泛型类型,而编译器无法确定这可能是什么.该版本中的可操作comment表示:

给定一个任意的约束T extends C,对于T的任何可能的实例化,几乎没有什么事情可以轻易确定——您不能只按原样使用C,而从另一边得到正确的答案.我们在将T作为一个子类型进行关联时有一些逻辑,但一旦你进行了像keyof T这样的逆变操作,所有的赌注都没有了.


无论如何,对于您的示例代码,我的方法是在可能的情况下坚持使用非条件非重映射类型,因为编译器在这方面的推理能力稍好一些(或者至少推迟了):

type IntersectKeys<T, U> = Pick<T, keyof T & keyof U>

这符合要求,或者至少推迟了判断,因此类似于false的内容不会被视为可分配给它:

function intersectFooBarKeys<U>(fb: FooBar, intersect: U): IntersectKeys<FooBar, U> {
  return false; // error! Type 'boolean' is not assignable to type 'IntersectKeys<FooBar, U>'
}

Playground link to code

Typescript相关问答推荐

net::BER_RECTION_REUSED和Uncatch使用Axios和TanStack-Query useMutation时未捕获错误(DelivercMutation)

React组件数组及其props 的类型定义

当类型断言函数返回假时,TypScript正在推断从不类型

是否可以根据嵌套属性来更改接口中属性的类型?

使某些类型的字段变为可选字段'

动态调用类型化函数

如何修复正在处理类型但与函数一起使用时不处理的类型脚本类型

React重定向参数

我可以为情态车使用棱角形的路由守卫吗?

如何在Angular 12中创建DisplayBlock组件?

如何使用类型谓词将类型限制为不可赋值的类型?

如何在Type脚本中动态扩展函数的参数类型?

部分更新类型的类型相等

如何通过TypeScrip中的函数防止映射类型中未能通过复杂推理的any值

如何在React组件中指定forwardRef是可选的?

如何将类似数组的对象(字符串::匹配结果)转换为类型脚本中的数组?

如何创建由一个帐户签名但使用另一个帐户支付的Hedera交易

我可以使用JsDocs来澄清Typescript实用程序类型吗?

将对象数组传递给 Typescript 中的导入函数

为什么 TypeScript 类型条件会影响其分支的结果?