Can I watch() the elements within a v-for separately?

44 Views Asked by At

I have v-for="pill in pills" loop that creates a set of panels, each of these panels includes a number that can be changed (either through +/- buttons or by directly editing them). All of the panels are in an Array of Objects (one of the properties of these Objects is that amount)

I would like to react when the amount changes in one of the panels (and make an HTTP call). Today I do that by watching the whole Array:

watch(() => pills.value, () => console.log('a pill changed'), { deep: true })

This is not practical, though, because I have to send all the objects one by one through the HTTP call above because I do not know which of them changed.

It would be great if I could somehow watch() each of the pill separately, and act upon changes on specific ones (i.e. send only the one that changed). Is this possible?

2

There are 2 best solutions below

1
Lynn On

eg 1

<template>
    <div></div>
</template>
<script>
    export default {
        data(){
            obj:{
                a:''
            },
        },
        watch:{
            /
            'obj.a'(item1,item2){
                
            },
            deep:true
        }
    }
</script>

eg 2

<template>
    <div></div>
</template>
<script>
    export default {
        data(){
            newArr:[
                {
                    list:[
                        {
                            label:null
                        }
                    ]
                }
            ]
        },
        methods:{
            show(){
                this.newArr[0].list[0].label = "2020年11月10日10:36:58";
            }
        },
        watch:{
            newArr:{
                handler(val){
                    // ...
                },
                deep:true
            },
        }
    }
</script>



0
Tachibana Shin On

this is a very common problem, the core team is solving in front of you can only use my way:

// </script><script type="module">
import { ref, watch } from "https://esm.run/vue@3"

const pills = ref([0])

function onChangeItem(i, newVal, oldVal) {
  console.log("index %i changed value %s -> %s", i, oldVal, newVal)
}

const watchers = new Map()
watch(() => pills.value.length, (newLength, $oldLength, onCleanup) => {
  const oldLength = $oldLength ?? 0
  if (newLength > oldLength) {
    for (let i = oldLength; i < newLength; i++) {
      watchers.set(i, watch(() => pills.value[i], (n, o) => {
        onChangeItem(i, n, o)
      }, { immediate: $oldLength !== undefined }))
    }
    return
  }
  if (newLength < oldLength) {
    for (let i = newLength; i < oldLength; i++) {
      watchers.get(i)?.()
      watchers.delete(i)
    }
  }
}, { immediate: true })

async function main() {
pills.value[0] += 10

await Promise.resolve()
pills.value.push(20)

await Promise.resolve()
pills.value.pop()
pills.value.pop()
}

main()

Note: that this is an unoptimized version of the cleanup you have to add onBeforeUnmount(() => watchers.forEach(item => item())) or something to cancel if the Effect listener end of life