nextTick,
defineComponent,
reactive,
- serializeInner
+ serializeInner,
+ shallowRef
} from '@vue/runtime-test'
// reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
await nextTick()
expect(spy.mock.calls[1][0]).toBe('p')
})
+
+ // #2078
+ test('handling multiple merged refs', async () => {
+ const Foo = {
+ render: () => h('div', 'foo')
+ }
+ const Bar = {
+ render: () => h('div', 'bar')
+ }
+
+ const viewRef = shallowRef<any>(Foo)
+ const elRef1 = ref()
+ const elRef2 = ref()
+
+ const App = {
+ render() {
+ if (!viewRef.value) {
+ return null
+ }
+ const view = h(viewRef.value, { ref: elRef1 })
+ return h(view, { ref: elRef2 })
+ }
+ }
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+
+ expect(serializeInner(elRef1.value.$el)).toBe('foo')
+ expect(elRef1.value).toBe(elRef2.value)
+
+ viewRef.value = Bar
+ await nextTick()
+ expect(serializeInner(elRef1.value.$el)).toBe('bar')
+ expect(elRef1.value).toBe(elRef2.value)
+
+ viewRef.value = null
+ await nextTick()
+ expect(elRef1.value).toBeNull()
+ expect(elRef1.value).toBe(elRef2.value)
+ })
})
isSameVNodeType,
Static,
VNodeNormalizedRef,
- VNodeHook
+ VNodeHook,
+ VNodeNormalizedRefAtom
} from './vnode'
import {
ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
vnode: VNode | null
) => {
+ if (isArray(rawRef)) {
+ rawRef.forEach((r, i) =>
+ setRef(
+ r,
+ oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
+ parentComponent,
+ parentSuspense,
+ vnode
+ )
+ )
+ return
+ }
+
let value: ComponentPublicInstance | RendererNode | null
if (!vnode) {
value = null
}
}
- const [owner, ref] = rawRef
+ const { i: owner, r: ref } = rawRef
if (__DEV__ && !owner) {
warn(
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
)
return
}
- const oldRef = oldRawRef && oldRawRef[1]
+ const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
const setupState = owner.setupState
| Ref
| ((ref: object | null, refs: Record<string, any>) => void)
-export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
+export type VNodeNormalizedRefAtom = {
+ i: ComponentInternalInstance
+ r: VNodeRef
+}
+
+export type VNodeNormalizedRef =
+ | VNodeNormalizedRefAtom
+ | (VNodeNormalizedRefAtom)[]
type VNodeMountHook = (vnode: VNode) => void
type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
key != null ? key : null
-const normalizeRef = ({ ref }: VNodeProps): VNode['ref'] => {
+const normalizeRef = ({ ref }: VNodeProps): VNodeNormalizedRefAtom | null => {
return (ref != null
? isArray(ref)
? ref
- : [currentRenderingInstance!, ref]
+ : { i: currentRenderingInstance, r: ref }
: null) as any
}
}
if (isVNode(type)) {
- const cloned = cloneVNode(type, props)
+ // createVNode receiving an existing vnode. This happens in cases like
+ // <component :is="vnode"/>
+ // #2078 make sure to merge refs during the clone instead of overwriting it
+ const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
export function cloneVNode<T, U>(
vnode: VNode<T, U>,
- extraProps?: Data & VNodeProps | null
+ extraProps?: Data & VNodeProps | null,
+ mergeRef = false
): VNode<T, U> {
// This is intentionally NOT using spread or extend to avoid the runtime
// key enumeration cost.
- const { props, patchFlag } = vnode
+ const { props, ref, patchFlag } = vnode
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
return {
__v_isVNode: true,
type: vnode.type,
props: mergedProps,
key: mergedProps && normalizeKey(mergedProps),
- ref: extraProps && extraProps.ref ? normalizeRef(extraProps) : vnode.ref,
+ ref:
+ extraProps && extraProps.ref
+ ? // #2078 in the case of <component :is="vnode" ref="extra"/>
+ // if the vnode itself already has a ref, cloneVNode will need to merge
+ // the refs so the single vnode can be set on multiple refs
+ mergeRef && ref
+ ? isArray(ref)
+ ? ref.concat(normalizeRef(extraProps)!)
+ : [ref, normalizeRef(extraProps)!]
+ : normalizeRef(extraProps)
+ : ref,
scopeId: vnode.scopeId,
children: vnode.children,
target: vnode.target,