0

Given the following Typescript interfaces, how should I set user to accept interface Baseball, Basketball, or Football, where properties vary?

Currently, the only properties accessible are those which overlap. In this example being user.stats.A.

I am working within Vue 3's Composition API.


interface Profile {
   firstName: string
   lastName: string
   status: string
}

export interface Baseball extends Profile { 
    stats { 
        A: string
        B: string
        ...
    }
}

export interface Basketball extends Profile {
    stats { 
        A: string
        C: string
        ...
    }
}

export interface Football extends Profile {
    stats { 
        A: string
        D: string
        ...
    }
}
setup(){
    const user = reactive<Baseball | Basketball | Football>({
        firstName: 'Michael'
        lastName: 'Jordan'
        status: 'GOAT'
        stats: {
            ...basketball specific properties
        }
    }
})
Ryan Prentiss
  • 1,492
  • 2
  • 25
  • 46

2 Answers2

2

What you have is perfectly fine. In fact you did the right thing. You will always have to provide at least the properties of interface of the union type. I would suggest to do something like this

// I(nterface)User
type IUser = Baseball | Basketball | Football;

When you are unsure about what object your getting the only thing you can do is test for the properties you need.

Here is an explanation of a custom XOR type. Maybe this can help you. This will not allow you to put A, B AND C but only the properties of a single interface.

type Without < T, U > = {
  [P in Exclude < keyof T, keyof U > ] ? : never
};
type XOR < T, U > = (T | U) extends object ? (Without < T, U > & U) | (Without < U, T > & T) : T | U;

interface Profile {
  firstName: string
  lastName: string
  status: string
  stats: XOR < XOR < Baseball, Basketball > , Football >
}

export interface Baseball {
  A: string
  B: string
}

export interface Basketball {
  A: string
  C: string
}

export interface Football {
  A: string
  D: string
}



type IUser = Profile

const user: IUser = {
  firstName: 'Michael',
  lastName: 'Jordan',
  status: 'GOAT',
  stats: {
    A: '',
    B: ''
  }
};
Vincent Menzel
  • 1,040
  • 1
  • 6
  • 17
  • Unfortunately, when attempting to access any property which is not mutually shared, I receive the `Property 'X' does not exist on type ' Vetur(2339)` error. – Ryan Prentiss Jul 31 '21 at 00:34
  • Sure I hope it helps. I can just recommend you to have a closer look to the other thread I linked. Im sorry to say that if this is an vue related issue I can't really help you out with that. – Vincent Menzel Jul 31 '21 at 01:09
1

A workaround is to move the object initialization outside of the reactive():

import { defineComponent, reactive } from 'vue'

//...

export default defineComponent({
  setup() {
    const initProfile: Baseball | Basketball | Football = {
      firstName: 'Michael',
      lastName: 'Jordan',
      status: 'GOAT',
      stats: {
        A: '1',
        C: '2',
      }
    }
    const user = reactive(initProfile)

    console.log(user.stats.C) // ✅
  }
})
tony19
  • 125,647
  • 18
  • 229
  • 307