Subscribe on changes!

Allow mergeProps to merge `ref` property

avatar
Jan 5th 2023

What problem does this feature solve?

Consider you are writing a hook that decorates element with property

// this will make dom element draggable with mouse with the force of css transform
const useDraggable = () => {
  const element = ref()
  onMounted(() => {
    // do something with element to get dimension of element... etc
  })
  return {
    domProperties: 'xxx',
    onSomeHandler: () => { /* ooo */ },
    ref: element
  }
}

The returned object contains handler and ref that it need to work with.

And you use it in this way

<template>
  <MyOtherComponent ref="comp" v-bind="props"></MyOtherComponent>
</template>
<script>
const comp = ref()
const props = useDraggable()
onMounted(() => {
  // do something with the `comp`
})
</script>

However, the comp ref don't work unless you specifically workaround it. Because mergeProps only merge class style onXXX.

The ref is simply got overwritten, so you get undefined in comp

What does the proposed API look like?

No API change, only implementation change in

https://github.com/vuejs/core/blob/c6e5bda27d13554675d68dbe33b07f3474467aa6/packages/runtime-core/src/vnode.ts#L816

to create a proxied ComputedRef that forward dom value to ref of every incoming props source

probably something like

export function mergeProps(...args: (Data & VNodeProps)[]) {
  const ret: Data = {}
  // ===== PATCH START =====
  const refs = []
  // ===== PATCH END=====
  for (let i = 0; i < args.length; i++) {
    const toMerge = args[i]
    for (const key in toMerge) {
      if (key === 'class') {
        if (ret.class !== toMerge.class) {
          ret.class = normalizeClass([ret.class, toMerge.class])
        }
      } else if (key === 'style') {
        ret.style = normalizeStyle([ret.style, toMerge.style])
  // ===== PATCH START =====
      } else if (key === 'ref') {
        ret[key] = toMerge[key]
        refs.push(toMerge.ref)
  // ===== PATCH END=====
      } else if (isOn(key)) {
        const existing = ret[key]
        const incoming = toMerge[key]
        if (
          incoming &&
          existing !== incoming &&
          !(isArray(existing) && existing.includes(incoming))
        ) {
          ret[key] = existing
            ? [].concat(existing as any, incoming as any)
            : incoming
        }
      } else if (key !== '') {
        ret[key] = toMerge[key]
      }
    }
  }
  // ===== PATCH START =====
  if (refs.length > 1) {
    const refProxy = computed({
      set (v) {
        for (const r of refs) {
          r.value = v
        }
      },
     get  () {
       return refs[0].value
     }
    })
    ret.ref = refProxy 
  } 
  // ===== PATCH END=====
  
  return ret
}

There may be more case because ref can be string or setter or undefined, but the basic idea is like above