TS2339: 属性在联合类型上不存在 - 属性字符串 | 未定义

10

我有一个联合类型的问题,它看起来像这样:

type RepeatForm = {
    step:
        | {
              repeat: false;
          }
        | {
              repeat: true;
              from: undefined;
          }
        | {
              repeat: true;
              from: string;
              by?: string;
          };
};

我有以下函数,如果存在by,我想获取它的值:

export const getByField = (form: RepeatForm) => {
    if (form.step.repeat === false || form.step.from === undefined) {
        return null;
    }
    const x = form.step.from;
    return form.step.by;
};

我遇到了这个错误:'by'属性在类型 '{ repeat: true; from: undefined; }' 上不存在。'{ repeat: true; from: string; by?: string | undefined; }'。 这对我来说非常令人困惑,因为TypeScript知道`form.step.from`与`undefined`不同,并且甚至将变量`x`的类型插值为`string`。
这个问题的原因是仅在`{ repeat: true; from: string; by?: string | undefined; }`对象中定义了`by`属性,并未在`{ repeat: true; from: undefined; }`对象中定义。您可以使用可选链语法(`?.`)来安全地访问`by`属性。
如下所示:
```typescript form?.step?.from?.by ```
这将检查每个嵌套属性是否存在,如果存在则返回其值,否则返回undefined。
1个回答

4

原始的PR中对于联合类型的区分字段必须是string字面量类型非常明确(虽然现在好像已经支持了booleannumber字面量类型)。因此,您基于字段类型(string vs undefined)进行区分的用例似乎不被支持。例如,以下代码不起作用:

let u!: { v: number, n: number } | { v: string, s: string}
if(typeof u.v === 'number') {
    u.n // not accesible, type not narrowed 
}

我们可以使用条件类型和自定义类型守卫来使事情正常工作:
function isUndefined<T, K extends keyof T>(value : T, field: K) : value is Extract<T, { [P in K] : undefined }> {
    return !!value[field]
}

export const getByField = (form: RepeatForm) => {
    if (form.step.repeat === false || isUndefined(form.step, 'from')) {
        return null;
    }
    const x = form.step.from;
    return form.step.by;
};

我们也可以创建一个通用版本的此函数,允许通过任何类型进行缩小:
type ExtractKeysOfType<T, TValue> = { [P in keyof T]: T[P] extends TValue ? P : never}[keyof T]

function fieldOfType<T, K extends ExtractKeysOfType<T, string>>(value : T, field: K, type: 'string'): value is Extract<T, { [P in K] : string }>
function fieldOfType<T, K extends ExtractKeysOfType<T, number>>(value : T, field: K, type: 'number'): value is Extract<T, { [P in K] : number }>
function fieldOfType<T, K extends ExtractKeysOfType<T, boolean>>(value : T, field: K, type: 'boolean'): value is Extract<T, { [P in K] : boolean }>
function fieldOfType<T, K extends ExtractKeysOfType<T, Function>>(value : T, field: K, type: 'function'): value is Extract<T, { [P in K] : Function }>
function fieldOfType<T, K extends ExtractKeysOfType<T, symbol>>(value : T, field: K, type: 'symbol'): value is Extract<T, { [P in K] : symbol }>
function fieldOfType<T, K extends ExtractKeysOfType<T, object>>(value : T, field: K, type: 'object'): value is Extract<T, { [P in K] : object }>
function fieldOfType<T, K extends ExtractKeysOfType<T, undefined>>(value : T, field: K, type: 'undefined'): value is Extract<T, { [P in K] : undefined }>
function fieldOfType<T, K extends keyof T, TValue extends T[K]>(value : T, field: K, type: new (...args:any[])=> TValue): value is Extract<T, { [P in K] : TValue }>
function fieldOfType<T, K extends keyof T>(value : T, field: K, type: string| Function) :boolean {
    if(typeof type === 'string') {
        return typeof value[field] === type;
    } else {
        return value[field] instanceof type
    }
}

const getByField = (form: RepeatForm) => {
    if (form.step.repeat === false || fieldOfType(form.step, 'from', 'undefined')) {
        return null;
    }
    const x = form.step.from;
    return form.step.by;
};


let u: { v: number, n: number } | { v: string, s: string}={ v: 0, n : 10};
if(fieldOfType(u, 'v', 'number')) {
    console.log(u.n);
}

class A {private a: undefined;}
class B {private b: undefined;}
let uc: { v: A, n: number } | { v: B, s: string} = Math.random() > 0.5 ? { v: new B(), s: '10' } : { v: new A(), n: 10 };
if(fieldOfType(uc, 'v', A)) {
    console.log(uc.n)
}

1
你知道TypeScript是否计划支持它吗? - Konrad Klimczak
1
@KonradKlimczak 抱歉,我不知道是否有支持此功能的计划,但可能存在问题,这似乎是一个常见的情况。 - Titian Cernicova-Dragomir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接