我编写了一个filterObject函数,接受一个对象和一个回调函数作为该函数的输入.对于回调函数,我描述了类型和类型保护.但由于某些原因,类型保护不能正常工作.

以下是功能代码

type Entry<T> = {
    [K in keyof T]: [K, T[K]];
}[keyof T];

const filterObject = <T extends { [key: PropertyKey]: T[keyof T] }, S extends T>(
    obj: T,
    callback: (val: Entry<T>) => val is Entry<S>,
) => {
    return Object.entries(obj).filter(callback);
};

这里我用类型保护调用filterObject函数

type A = {
    a?: number;
    b: number;
};

const obj: A = {
    a: 1,
    b: 2,
};

const data = filterObject(obj, (val): val is Entry<Required<A>> => {
    return true;
});

执行filterObject功能后. 变量data的类型是[string, number | undefined][],但理论上应该是[string, number][]. 由于回调类型保护设置为(val is Entry<Required<A>>)Required<A>删除了可选标志(?)从对象字段

Expected behavior: the variable data type is [string, number][]
Actual behavior: the variable data type is [string, number | undefined][]

链接到playground

推荐答案

让我们来看看为什么filterObject()不返回Array<Entry<S>>.

the Object.entries() methodTypeScript library definition看起来像是

interface ObjectConstructor {
  entries<T>(o: { [s: string]: T; } | ArrayLike<T>): [string, T][];
}

这意味着当您对类型为constrained{[s: string]: T[keyof T]}obj调用Object.entries(obj)时,它将返回类型Array<[string, T[keyof T]]>的值.

在该结果中,您可以使用类型为(val: Entry<T>) => val is Entry<S>的回调调用filter().filter()TypeScript library definition看起来像

interface Array<U> {
  filter<V extends U>(cb: (v: U, i: number, a: U[]) => value is V, thisArg?: any): V[];
  filter(cb: (v: U, i: number, a: U[]) => unknown, thisArg?: any): U[];
}

如果希望filterObject()返回Array<Entry<S>>,则需要调用filter()的第一个调用签名.因此,您需要使用类型参数Entry<S>实例化其类型参数V.但这将需要Entry<S>来扩展U,而U已经被实例化为[string, T[keyof T]].但事实并非如此.Entry<S>的第一个元素仅被约束为string | number | symbol,因此不被视为可赋值给string.

因此,第一个调用签名被跳过,所有的东西很快就会从这里消失.第二个调用签名没有进行任何限制,因此整个函数filterObject()的输出类型只有[string, T[keyof T]][]……然后,所有的希望都破灭了.您的后续调用(其中obj是类型A)将始终生成Array<[string, number | undefined]>,因为A[keyof A]number | undefined.哎呀.


有多种方法可以解决这个问题.

我认为对于您来说,最简单的是Object.entries(obj)返回Array<Entry<T>>,因为Entry<S> is可赋值给Entry T,并且第一个调用签名成功:

const filterObject = <T extends { [key: PropertyKey]: T[keyof T] }, S extends T>(
    obj: T,
    callback: (val: Entry<T>) => val is Entry<S>,
) => {
    const entries: Array<Entry<T>> = Object.entries(obj);
    return entries.filter(callback);
};

现在filterObject()返回Array<Entry<S>>,您会得到您正在寻找的缩小范围的行为:

type A = { a?: number; b: number; };
const obj: A = { a: 1, b: 2, };
const data = filterObject(obj, (val): val is Entry<Required<A>> => true)
const res = data[0][1]; // number

您可以try 修改Entry的定义或T类型参数的约束并使调用成功,但上面的断言非常简单,可以完成工作.

Playground link to code

Typescript相关问答推荐

类型脚本如何将嵌套的通用类型展开为父通用类型

我应该使用什么类型的React表单onsubmit事件?

我用相同的Redux—Toolkit查询同时呈现两个不同的组件,但使用不同的参数

有没有办法解决相互影响的路由之间的合并?

如何使用函数填充对象?

是否使用非显式名称隔离在对象属性上声明的接口的内部类型?

CDK从可选的Lambda角色属性承担角色/复合主体中没有非空断言

是否将自动导入样式从`~/目录/文件`改为`@/目录/文件`?

刷新时保持当前名称的Angular

跟踪深度路径时按条件提取嵌套类型

将对象的属性转换为正确类型和位置的函数参数

如何在不违反挂钩规则的情况下动态更改显示内容(带有状态)?

在Reaction中折叠手风琴

如何从上传文件中删除预览文件图标?

如何正确地将元组表格输入到对象数组实用函数(如ZIP)中,避免在TypeScrip 5.2.2中合并所有值

两个名称不同的相同打字界面-如何使其干燥/避免重复?

是否使用类型将方法动态添加到类?

在ts中获取级联子K类型?

如何在TypeScript中声明逆变函数成员?

广义泛型类型不加载抽象类型上下文