TS2339: Property does not exist on union type - property string | undefined

女生的网名这么多〃 提交于 2020-06-24 12:31:05

问题


I have problem with my union type, which looks like this:

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

And I have following function where I want to get value of by if it's there:

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;
};

I get this error: Property 'by' does not exist on type '{ repeat: true; from: undefined; } | { repeat: true; from: string; by?: string | undefined; }'. Property 'by' does not exist on type '{ repeat: true; from: undefined; }'.

Which is super confusing for me, because TypeScript know that form.step.from is different from undefined and he even interpolates type of variable x to string.

What's the reason of this issue? How can I access by property then?


回答1:


The original PR for discriminated unions is very specific about the fact that the discriminating field must be a string literal type (with the option to add support for boolean and number literal types which seems to have happened). So your use case where you are discriminating based on the type of the field (string vs undefined) does not appear to be supported. This does not work for example:

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

We could use conditional types and a custom type guard to make things work:

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;
};

We can also create a general version of this function that allows narrowing by any type:

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)
}


来源:https://stackoverflow.com/questions/50840685/ts2339-property-does-not-exist-on-union-type-property-string-undefined

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!