fix(runtime-core/refs): handle multiple merged refs for dynamic component with vnode
fix #2078
This commit is contained in:
parent
313dd06065
commit
612eb6712a
@ -6,7 +6,8 @@ import {
|
||||
nextTick,
|
||||
defineComponent,
|
||||
reactive,
|
||||
serializeInner
|
||||
serializeInner,
|
||||
shallowRef
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
|
||||
@ -325,4 +326,43 @@ describe('api: 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)
|
||||
})
|
||||
})
|
||||
|
@ -10,7 +10,8 @@ import {
|
||||
isSameVNodeType,
|
||||
Static,
|
||||
VNodeNormalizedRef,
|
||||
VNodeHook
|
||||
VNodeHook,
|
||||
VNodeNormalizedRefAtom
|
||||
} from './vnode'
|
||||
import {
|
||||
ComponentInternalInstance,
|
||||
@ -284,6 +285,19 @@ export const setRef = (
|
||||
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
|
||||
@ -295,7 +309,7 @@ export const setRef = (
|
||||
}
|
||||
}
|
||||
|
||||
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. ` +
|
||||
@ -303,7 +317,7 @@ export const setRef = (
|
||||
)
|
||||
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
|
||||
|
||||
|
@ -64,7 +64,14 @@ export type VNodeRef =
|
||||
| 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
|
||||
@ -289,11 +296,11 @@ export const InternalObjectKey = `__vInternal`
|
||||
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
|
||||
}
|
||||
|
||||
@ -317,7 +324,10 @@ function _createVNode(
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -429,11 +439,12 @@ function _createVNode(
|
||||
|
||||
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,
|
||||
@ -441,7 +452,17 @@ export function cloneVNode<T, U>(
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user