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

前端 未结 1 718
终归单人心
终归单人心 2021-02-09 08:03

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

type RepeatForm = {
    step:
        | {
              repeat: false;
          }
        | {
                 


        
相关标签:
1条回答
  • 2021-02-09 08:55

    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)
    }
    
    0 讨论(0)
提交回复
热议问题