vuejs / pinia

🍍 Intuitive, type safe, light and flexible Store for Vue using the composition api with DevTools support
https://pinia.vuejs.org
MIT License
13.02k stars 1.04k forks source link

Return types of state variables with nested ref/computed properties don't match the specified type #2695

Closed DanRiess closed 3 months ago

DanRiess commented 3 months ago

Reproduction

https://play.pinia.vuejs.org/#eNqVVFFP2zAQ/ismL22lNgExMalqEWxiEtO0TcBbw0NILsWQnC3bKUVV//vOdpImpWPjpY3Pd999332XbIJLKcNVBcE0mOlUcWmYBlNJViS4nMeB0XFwHmMUsfQR0meWigoNqNBolgvFYJ2UsoAYZ5Gvplw6GKBoYoBOs6hzCMYBL6VQZlImMnzSAqnvJkbG4vqC2k2Zi9iY5MgTG4mDyD2HoMvJgxIvmjg8UfK4Sb0gEVEGKyNEoSeJ5L7s0Ripp1GUZkj5GRR8pUIEE6Esozc1F2fhWXgSFfwhokYRxwzW/TZUMcmg/B/0JvXiODz5FH52qKvTGrS0qBZ0G+OWxmJ0KjDny72hpKKUvAD1SxousD+cpCjEy3cXM6qClqIz6kD8Sa89698KaHwr6MgyiVqC8ddXtz9hTc/tZSmyijx+7/IGtCgqy9GnfakwI9qdPMf22nnMcXmnr9YGUDeiLFE3DZfvxvz1Hek7uqfhaWeKu+2kEfqFYhuWQc4Rbo1QMGba/t2JG8g127JciZIN3GoNYmwrbO/KQEZZY+Z+FORtOpGj5BjNqwR2TevN5p4aJiVMqYMihfYsFU8p0EGbYVU+gKJXgRjXCD+4tiNpQHjWhUjpxVkK9dqNcWqpp67z4r5GgrWjTkukDau0V0uQHe3DQT2ewZgNR2x+7vv5ksKTIFjLsqa0uD8nCJI+XNyPWKL37mxfxvIKU+sQ06KEb3iZpkCO4fKyThzW0NNG6Kjx0XYWBYSFaJNGtZk9YLK6LtXEuy5W9JVS2PLu1NU3m/aOTD9MbdyFdjtE/WP0E3GLQvKbYQ47d37akLW1c58eNi0ZfTBRGNY12Hrly3fUrK3tPu6YNHg+PnTPox7mvhMemOSQv29Y9QdIMHseH1llPvVvJu4rXhzTRhASKCXUv4oPaAxXSVHBh1D64voA4vko2P4Bvrdf2Q==

Steps to reproduce the bug

This issue might be related to #2658, but the linked PR does not solve it.

If a state variable contains a prop that is itself a ref or a computed value, the return type of that variable will 'simplify' that away (i.e. a prop with type ComputedRef will become just number).

That means, however, that using the return type of this state variable as a parameter in a function will throw a ts error.

A workaround is to expose the state variable through a getter in the store, but I kinda hate that because it breaks consistency. And since the state variable has to be exported to keep reactivity, there is no way of knowing if I can access it directly or have to use the getter without looking into the store, where it may or may not be documented.

type Item = {
  name: string
  price: ComputedRef<number>
}

type Listing = {
  id: string
  category: string
  items: Item[]
}

export const useStore = defineStore('counter', () => {
  const listings: Ref<Listing[]> = ref([])

  function someFnAcceptingAListing(listing: Listing) {
    console.log(listing)
  }

  function getListings() {
    return listings
  }

  return { listings, someFnAcceptingAListing, getListings }
})

const store = useStore()

// Access listings in different ways.
const exportedListings = store.listings // ❌ not type Listing[]
const { listings: storeToRefsListings } = storeToRefs(store) // ❌ not type Ref<Listing[]>
const getterListings = store.getListings() // βœ… Ref<Listing[]> !

// Try to pass listings to a function that should accept them as parameter.
// All of these work fine in runtime, but some the first 2 throw a ts error.
store.someFnAcceptingAListing(exportedListings[0]) // ❌ error
store.someFnAcceptingAListing(storeToRefsListings.value[0]) // ❌ error
store.someFnAcceptingAListing(getterListings.value[0]) // βœ… ok!

Expected behavior

All 3 methods of accessing the state variable should result in the same return type.

Actual behavior

I suppose returning the state variable directly triggers some internal unwrapping, which breaks the type? Anyways, returning such a complex typed variable changes the original type.

Additional information

No response