TypeScript - 如何过滤对象?

nit*_*ter 2 typescript

我正在尝试为以下函数进行正确的输入:

export function filter(obj: any, predicate: (value: any, key: string) => boolean) {
    const result: any = {};
    Object.keys(obj).forEach((name) => {
        if (predicate(obj[name], name)) {
            result[name] = obj[name];
        }
    });
    return result;
}
Run Code Online (Sandbox Code Playgroud)

是否可以保留打字内容?

jca*_*alz 5

如果没有有关用例的更多具体信息,我倾向于这样做:

function filter<T extends object>(
  obj: T,
  predicate: <K extends keyof T>(value: T[K], key: K) => boolean
) {
  const result: { [K in keyof T]?: T[K] } = {};
  (Object.keys(obj) as Array<keyof T>).forEach((name) => {
    if (predicate(obj[name], name)) {
      result[name] = obj[name];
    }
  });
  return result;
}
Run Code Online (Sandbox Code Playgroud)

我们想要filter()接受泛型类型obj的参数,这意味着您将只过滤对象类型而不是基元,并且您希望编译器跟踪实际的键值关系,而不是将其一直扩展到。因此,回调需要接受适用于 的参数;因此我们将其设为参数类型的通用回调,其中是对应的属性值类型。有关该表示法的更多信息,请参阅查找类型的文档。T extends objectTobjectpredicatevaluekeyTK extends keyof TkeyvalueT[K]keyof

对于结果,我们希望返回一个属性与 相同T但可选的对象,因为我们不知道哪些属性实际上存在。这可以表示为映射类型 { [K in keyof T]? T[K] },也称为Partial<T>

一个问题是我必须使用类型断言来告诉编译器我期望Object.keys(obj)Array<keyof T>( 的键的数组T)而不仅仅是string[]。这个期望可能会被违反,这就是为什么首先Object.keys()返回;string[]请参阅另一个 SO 问题以获取对此的解释。我认为在这里做出这个假设是合理的,但我将向您展示一种如果违反假设可能导致运行时错误的方法。


首先让我们测试所需的行为:

const obj = filter({ a: "hello", b: 123, c: true }, (v, k) => k === "b");
/* const obj: {
    a?: string | undefined;
    b?: number | undefined;
    c?: boolean | undefined;
} */
console.log(obj); // {b: 123}
Run Code Online (Sandbox Code Playgroud)

看起来不错; 编译器对参数感到满意,filter()并返回带有可选属性的值。

这是糟糕的情况:

interface Foo {
  x: string,
  y: string,
}
interface Bar extends Foo {
  z: number;
}
const bar: Bar = { x: "hello", y: "goodbye", z: 100 };
const foo: Foo = bar; // acceptable because Bar extends Foo
filter(foo, (v, k) => v === v.toLowerCase() ); // compiles fine, but
//  RUNTIME TypeError: v.toLowerCase is not a function
Run Code Online (Sandbox Code Playgroud)

编译器没有意识到foo具有number-valued 属性,因为我们已经扩展barFoo,但predicate回调实际上依赖vstring. 编译后,您会收到运行时错误。如果您担心这种可能性,那么您可能需要更严格的类型来filter()强制predicate真正采用 type 的值(value: unknown, key: PropertyKey),但这使用起来会更烦人。这实际上取决于用例。

Playground 代码链接