传递给函数时对象中键的打字稿缩小

Typescript narrowing of keys in objects when passed to function

为什么 TypeScript 不将对象成员的类型缩小应用到对象类型本身,以便它可以传递给另一个需要缩小类型的函数?这怎么可能 fixed/circumvented 而不会失去类型安全性?

最小示例:

type A = { key: string | null};
type B = {key: string};

function func(a: B) {};

const a: A = {key:'abcd'};
if(typeof(a.key)==='string') {
  a.key // has (narrowed) type 'string'
  func(a); // still does not work
}

错误信息是:Types of property 'key' are incompatible. Type 'string | null' is not assignable to type 'string'.

Playground-Link

使用类型保护:https://www.typescriptlang.org/docs/handbook/advanced-types.html#typeof-type-guards

type A = { key: string | null};
type B = {key: string};

function func(a: B) {};

const a: A = {key:'abcd'};

const isB = (foo: A | B): foo is B => typeof a.key === "string";
if(isB(a)) {
  a.key
  func(a);
}

TS Playground

这最终是一种 design limitation of TypeScript. It does not use type guards on an object's properties to narrow the object itself, except in the particular case where the object is a discriminated union 类型...而在您的情况下,A 根本不是一个联合体,更不用说一个受歧视的联合体了。

我会为你的情况做这个的方法是引入一个 user-defined type guard function 它明确地执行你期望的缩小:你传入一个带有 key 属性 的对象, return truefalse 取决于此对象是否有效 B:

const isB = (x: { key: any }): x is B => typeof x.key === "string";

那你就用它:

if (isB(a)) {
  func(a); // okay
}

这与您的代码(提供或接受函数调用)本质上是相同的逻辑,但编译器现在可以识别您的意图。好的,希望有所帮助;祝你好运!

Link to code

我很好奇并找到了缩小对象 属性 的方法。它并没有缩小到 B 类型,但是 Typescript 是一种具有结构类型的语言,因此该解决方案非常有效。

const isPropString = <P extends string, T extends Record<P, T[P]>>(
    prop: P,
    object: T,
): object is T & Record<P, string> => {
    return typeof object[prop] === 'string';
};

如果您想根据给定类型缩小对象的任何 属性 怎么办? @gwin-pin 有开胃菜,但这里是:

const isValidPropType = <T extends Record<P, T[P]>, P extends keyof T, Z extends T[P]>(
  object: T,
  prop: P,
  typeofParam: Z
): object is T & Record<P, Z> => {
  return typeof object[prop] === typeof typeofParam;
};

TS Playground 使用原题。