Typescript:当键为宽类型时,是否定义对象属性的类型保护?

Leo*_*ang 5 javascript typescript

我有一个返回对象属性是否为 的函数undefined。我需要这个功能,而不是仅仅做,obj[key] === undefined因为否则有时我会得到Property 'foo' does not exist on type 'Bar'.. 当属性键是文字时,编写类型很简单。IE:

function hasDefinedProp<
  Obj extends Partial<Record<string, any>>,
  Prop extends string,
>(
  obj: Obj,
  prop: Prop,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
  return obj[prop] !== undefined;
}

const obj: Partial<Record<string, number>> = {};
if (hasDefinedProp(obj, 'foo')) {
    obj.foo + 1; // No error.
    obj.bar + 1; // "Object is possibly 'undefined'."
}
Run Code Online (Sandbox Code Playgroud)

但是,当键的类型是宽类型时,这不起作用,即:

const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
    obj[key] + 1; // No error.
    obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
Run Code Online (Sandbox Code Playgroud)

是否可以使类型保护适用于宽类型?

cap*_*ian 3

AFAIK,这是不可能的。一旦您添加了显式string类型const key: string = '';- TS 就无法缩小 的文字类型key。因此,您可以使用任何想要访问属性的字符串。本例中TS 无法用 type 来区分两种类型string

const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
    obj[key] + 1; // No error.
    obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
Run Code Online (Sandbox Code Playgroud)

这让我想起了一个显式类型和不可变类型断言的例子:

const record: Record<string, number> = {
    a: 1
} as const;

type Keys = keyof typeof record // string instead of "a"
Run Code Online (Sandbox Code Playgroud)

这意味着在这种特殊情况下,禁止使用宽类型可能是个好主意:

type ForbidWide<Prop> = Prop extends string ? string extends Prop ? never : Prop : never


function hasDefinedProp<
    Obj extends Partial<Record<string, any>>,
    Prop extends string,
    >(
        obj: Obj,
        prop: ForbidWide<Prop>,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
    return obj[prop] !== undefined;
}


const obj: Partial<Record<string, number>> = {};
const key: string = '';
hasDefinedProp(obj, key) // error
Run Code Online (Sandbox Code Playgroud)

操场