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,
|
nextTick,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
reactive,
|
reactive,
|
||||||
serializeInner
|
serializeInner,
|
||||||
|
shallowRef
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
|
|
||||||
// reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
|
// reference: https://vue-composition-api-rfc.netlify.com/api.html#template-refs
|
||||||
@ -325,4 +326,43 @@ describe('api: template refs', () => {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
expect(spy.mock.calls[1][0]).toBe('p')
|
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,
|
isSameVNodeType,
|
||||||
Static,
|
Static,
|
||||||
VNodeNormalizedRef,
|
VNodeNormalizedRef,
|
||||||
VNodeHook
|
VNodeHook,
|
||||||
|
VNodeNormalizedRefAtom
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
@ -284,6 +285,19 @@ export const setRef = (
|
|||||||
parentSuspense: SuspenseBoundary | null,
|
parentSuspense: SuspenseBoundary | null,
|
||||||
vnode: VNode | 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
|
let value: ComponentPublicInstance | RendererNode | null
|
||||||
if (!vnode) {
|
if (!vnode) {
|
||||||
value = null
|
value = null
|
||||||
@ -295,7 +309,7 @@ export const setRef = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [owner, ref] = rawRef
|
const { i: owner, r: ref } = rawRef
|
||||||
if (__DEV__ && !owner) {
|
if (__DEV__ && !owner) {
|
||||||
warn(
|
warn(
|
||||||
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
|
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
|
||||||
@ -303,7 +317,7 @@ export const setRef = (
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const oldRef = oldRawRef && oldRawRef[1]
|
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
|
||||||
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
|
||||||
const setupState = owner.setupState
|
const setupState = owner.setupState
|
||||||
|
|
||||||
|
@ -64,7 +64,14 @@ export type VNodeRef =
|
|||||||
| Ref
|
| Ref
|
||||||
| ((ref: object | null, refs: Record<string, any>) => void)
|
| ((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 VNodeMountHook = (vnode: VNode) => void
|
||||||
type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
|
type VNodeUpdateHook = (vnode: VNode, oldVNode: VNode) => void
|
||||||
@ -289,11 +296,11 @@ export const InternalObjectKey = `__vInternal`
|
|||||||
const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
|
const normalizeKey = ({ key }: VNodeProps): VNode['key'] =>
|
||||||
key != null ? key : null
|
key != null ? key : null
|
||||||
|
|
||||||
const normalizeRef = ({ ref }: VNodeProps): VNode['ref'] => {
|
const normalizeRef = ({ ref }: VNodeProps): VNodeNormalizedRefAtom | null => {
|
||||||
return (ref != null
|
return (ref != null
|
||||||
? isArray(ref)
|
? isArray(ref)
|
||||||
? ref
|
? ref
|
||||||
: [currentRenderingInstance!, ref]
|
: { i: currentRenderingInstance, r: ref }
|
||||||
: null) as any
|
: null) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +324,10 @@ function _createVNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isVNode(type)) {
|
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) {
|
if (children) {
|
||||||
normalizeChildren(cloned, children)
|
normalizeChildren(cloned, children)
|
||||||
}
|
}
|
||||||
@ -429,11 +439,12 @@ function _createVNode(
|
|||||||
|
|
||||||
export function cloneVNode<T, U>(
|
export function cloneVNode<T, U>(
|
||||||
vnode: VNode<T, U>,
|
vnode: VNode<T, U>,
|
||||||
extraProps?: Data & VNodeProps | null
|
extraProps?: Data & VNodeProps | null,
|
||||||
|
mergeRef = false
|
||||||
): VNode<T, U> {
|
): VNode<T, U> {
|
||||||
// This is intentionally NOT using spread or extend to avoid the runtime
|
// This is intentionally NOT using spread or extend to avoid the runtime
|
||||||
// key enumeration cost.
|
// key enumeration cost.
|
||||||
const { props, patchFlag } = vnode
|
const { props, ref, patchFlag } = vnode
|
||||||
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
|
||||||
return {
|
return {
|
||||||
__v_isVNode: true,
|
__v_isVNode: true,
|
||||||
@ -441,7 +452,17 @@ export function cloneVNode<T, U>(
|
|||||||
type: vnode.type,
|
type: vnode.type,
|
||||||
props: mergedProps,
|
props: mergedProps,
|
||||||
key: mergedProps && normalizeKey(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,
|
scopeId: vnode.scopeId,
|
||||||
children: vnode.children,
|
children: vnode.children,
|
||||||
target: vnode.target,
|
target: vnode.target,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user