Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

watch callback arg has incorrect type for reactive array containing refs #9416

Closed
Dunqing opened this issue Oct 17, 2023 · 7 comments · Fixed by #11036
Closed

watch callback arg has incorrect type for reactive array containing refs #9416

Dunqing opened this issue Oct 17, 2023 · 7 comments · Fixed by #11036
Labels
🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. scope: types

Comments

@Dunqing
Copy link
Contributor

Dunqing commented Oct 17, 2023

Vue version

latest

Link to minimal reproduction

https://play.vuejs.org/#eNp9UstOwzAQ/JWVL6RS1PK4VQUJEAc4AAJumEOabluXxLbsdVoU5d9ZO6GABL34sTPjGa/diktrx01AMRUzXzplCTxSsBdSq9oaR9DCtqBynYPDZRyKklSD0MHSmRqOWHsktdSl0Z5Ah3qOzsP5npi9si47HiV9djLMp6O3kdTp4GzQ5JA1RRVwBOcX0EoNMJnAyxqBPiyCWQLxOjFA+cHo9S2HeaDf0H+G8ciY0lQ4rsxqcJO6y3s7Vde4UAXhFMgFzBlhdDbp28IN4Q1hbSum8G62UA1PbTvk77pI7ouzyZ4ockGebZdqNd54o7nRyU2K0tRWVegeLCmOJcW0zxGxoqrM9i7Vhix9vVxj+f5HfeN3sSbFo0OPrkEp9hgVboXUwzfP97jj9R6szSJUzD4APiG3LMSMPe0q6AXH/sFLaW/Td1F69eJvdoTaf10qBo3MLvGl4B9zfeDq33HPxmdJxw8huk88WeOl

Steps to reproduce

  1. Open the link
  2. Open the devtools and look for the console
  3. You will see [ref(0), ref(1), ref(2)] in console, but the type is number[]

What is expected?

I am not sure if the type is wrong or the value of the callback of the watch is wrong

What is actually happening?

I am not sure, you can see the above

System Info

Nothing

Any additional comments?

I would like to send a PR to fix this problem, but I need to know which solution is right first

@Alfred-Skyblue
Copy link
Member

By design, rawValue should be obtained, but it is a breaking change.

@Dunqing
Copy link
Contributor Author

Dunqing commented Oct 17, 2023

By design, rawValue should be obtained, but it is a breaking change.

Yes, I had a PR do this

@Okysu
Copy link

Okysu commented Oct 17, 2023

The type of numbers is essentially still Ref<number>[], but it is possible that plugins like Volar have inferred the type and directly destructure the .value member of Ref to obtain the original value, thus inferring the type as number[]. From my understanding, this should not be considered an error in the Vue mechanism, but rather a mistake in type inference. From the perspective of JavaScript itself, it should indeed output [ref(0), ref(1), ref(2)].

You can perform an experiment by using forEach to iterate through the array members and try to access their .value field:

<script lang="ts" setup>
import { watch, ref, reactive } from "vue";

const numbers = reactive([ref(0), ref(1), ref(2)]);
watch(
  numbers,
  (value) => {
    // The type hint may indicate that there is no .value field, but the console output is still correct.
    value.forEach((e) => console.log(e.value));
  },
  {
    immediate: true,
  }
);
</script>

@Alfred-Skyblue
Copy link
Member

Alfred-Skyblue commented Oct 17, 2023

@Dunqing, I apologize, I've found that, as @Okysu mentioned, this is a reactive type of issue.

When we use watch([ref(0), ref(1), ref(2)], callback), we do not encounter this issue. However, reactive([ref(0), ref(1), ref(2)]) is inferred as the same type. Perhaps we should avoid using reactive to create reactive arrays; we do not encounter this problem when using ref.

@pikax
Copy link
Member

pikax commented Oct 17, 2023

This seems to be working as expected:

describe('should unwrap tuple correctly', () => {
  const readonlyTuple = [ref(0)] as const
  const reactiveReadonlyTuple = reactive(readonlyTuple)
  expectType<Ref<number>>(reactiveReadonlyTuple[0])

  const tuple: [Ref<number>] = [ref(0)]
  const reactiveTuple = reactive(tuple)
  expectType<Ref<number>>(reactiveTuple[0])
})

https://github.com/vuejs/core/blob/main/packages/dts-test/reactivity.test-d.ts#L55-L64

const numbers = reactive([ref(0), ref(1), ref(2)]); // [ ref(0), ...]

When is used in conjunction with watch it will monitor the array changes, so making it not the same as watch([ref(0)] because you're watching the reactive and not passing an array to watch

https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts#L211-L214

@liulinboyi
Copy link
Member

I have a try with your example, If you write a getter function in the watch like the follow code:

<script setup lang="ts">
import { watch, ref, reactive } from 'vue'

const numbers = reactive([ref(0), ref(1), ref(2)])
watch(() => numbers, (value) => {
  // The type of the value is number[], but the value is [ref(0), ref(1), ref(2)]
  console.log(value)
}, {
  immediate: true,
})
</script>

<template>
  <div>
    {{ numbers }}
  </div>
</template>

image
The type is right.

https://play.vuejs.org/#__DEV__eNp9UsFO3DAQ/ZWRL2SlaNOW22oXqa04tIcWATfMIWRnswbHtuxxWBTl3xk7SwAJUKR4PO/NzPOzB/HTuWUfUazEOjReOYKAFB3o2rQbKShIcSaN6pz1BAM81tTsS/C4S7+6IdUjjLDztoMT7nMiTfoaawKBid0d+gCbmVrccGXxbZE7FN+P64/F7UKa3LooFrA5e6ksoehrHTHnBmkAqgqu9wj05BDsDojjzAAVjkU3tyXcRXoPfTY2tUxarcaltu1xmjRjOY1TXYdbVROugHzEkhFG19VkFRvDG8LOaabwDmC9VX0OAIZhNmAcM1ZN4LqaS0TJDrOAnWqX98EavoY8V4rGdk5p9P8dKRYoxWpSlLBaa/v4N+eOqqZ8s8fm4YP8fTiknBQXHgP6HqWYMap9izTB51f/8MDxDHZ2GzWzvwAvkc2LSeNE+xXNlmW/4WW1f/IDUqa9DucHQhNeDpWEJuaY+VLwG/r9xdFf5Z4uT3MdX4kYnwGWr+qd

@skirtles-code
Copy link
Contributor

I believe the runtime behaviour is correct, but there does seem to be a problem with the types.

To recap:

  1. A reactive array does not automatically unwrap any refs it contains.
  2. watch unwraps a ref source when passing values to the callback.
  3. Passing a reactive object to watch will create a deep watcher on that object. A reactive array counts as a reactive object.
  4. A non-reactive array passed to watch is treated as multiple sources to be watched.

However, it seems that the types for watch aren't making a distinction between cases 3 and 4 for arrays:

import { ref, reactive, watch } from 'vue'

const num = ref(0)
const arr = reactive([num])

watch(arr, val => {
  // val is an array containing the ref, but the types don't reflect that
  console.log(val[0].value) // incorrect types
}, { immediate: true })

watch([num], val => {
  // val is an array containing the value of ref, not the ref itself
  console.log(val[0]) // correct types
}, { immediate: true })

Both of these playgrounds give TS errors for val[0].value because they are treating the ref as a number:

@yyx990803 yyx990803 added scope: types 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels May 27, 2024
@yyx990803 yyx990803 changed the title value of the callback of the watch is incorrect watch callback arg has incorrect type for reactive array containing refs May 27, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Jun 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. scope: types
Projects
None yet
7 participants