+++ /dev/null
-import { baseParse as parse } from '../../src/parse'
-import { transform } from '../../src/transform'
-import { transformRef } from '../../src/transforms/transformRef'
-import { ElementNode, NodeTypes } from '../../src/ast'
-
-function transformWithRef(template: string) {
- const ast = parse(template)
- transform(ast, {
- nodeTransforms: [transformRef]
- })
- return ast.children[0] as ElementNode
-}
-
-describe('compiler: transform ref', () => {
- const getExpected = (key: any) => ({
- type: NodeTypes.DIRECTIVE,
- name: 'bind',
- arg: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `ref`
- },
- exp: {
- type: NodeTypes.COMPOUND_EXPRESSION,
- children: [`[_ctx, `, key, `]`]
- }
- })
-
- test('static', () => {
- const node = transformWithRef(`<div ref="test"/>`)
- expect(node.props[0]).toMatchObject(
- getExpected({
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `test`,
- isStatic: true
- })
- )
- })
-
- test('dynamic', () => {
- const node = transformWithRef(`<div :ref="test"/>`)
- expect(node.props[0]).toMatchObject(
- getExpected({
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: `test`,
- isStatic: false
- })
- )
- })
-})
import { generate, CodegenResult } from './codegen'
import { RootNode } from './ast'
import { isString } from '@vue/shared'
-import { transformRef } from './transforms/transformRef'
import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression'
): TransformPreset {
return [
[
- transformRef,
transformOnce,
transformIf,
transformFor,
+++ /dev/null
-import { NodeTransform } from '../transform'
-import {
- NodeTypes,
- ElementTypes,
- createSimpleExpression,
- createCompoundExpression
-} from '../ast'
-import { findProp } from '../utils'
-
-// Convert ref="foo" to `:ref="[_ctx, 'foo']"` so that the ref contains the
-// correct owner instance even inside slots.
-export const transformRef: NodeTransform = node => {
- if (
- !(
- node.type === NodeTypes.ELEMENT &&
- (node.tagType === ElementTypes.ELEMENT ||
- node.tagType === ElementTypes.COMPONENT)
- )
- ) {
- return
- }
- const ref = findProp(node, 'ref')
- if (!ref) return
- const refKey =
- ref.type === NodeTypes.ATTRIBUTE
- ? ref.value
- ? createSimpleExpression(ref.value.content, true, ref.value.loc)
- : null
- : ref.exp
- if (refKey) {
- node.props[node.props.indexOf(ref)] = {
- type: NodeTypes.DIRECTIVE,
- name: `bind`,
- arg: createSimpleExpression(`ref`, true, ref.loc),
- exp: createCompoundExpression([`[_ctx, `, refKey, `]`]),
- modifiers: [],
- loc: ref.loc
- }
- }
-}
}
},
render() {
- // Note: string refs are compiled into [ctx, key] tuples by the compiler
- // to ensure correct context.
- return h('div', { ref: [this, 'refKey'] as any })
+ return h('div', { ref: 'refKey' })
}
}
render(h(Comp), root)
}
},
render() {
- return h('div', { ref: [this, refKey.value] as any })
+ return h('div', { ref: refKey.value })
}
}
render(h(Comp), root)
}
},
render() {
- return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null
+ return toggle.value ? h('div', { ref: 'refKey' }) : null
}
}
render(h(Comp), root)
await nextTick()
expect(el.value).toBe(null)
})
+
+ test('string ref inside slots', async () => {
+ const root = nodeOps.createElement('div')
+ const spy = jest.fn()
+ const Child = {
+ render(this: any) {
+ return this.$slots.default()
+ }
+ }
+
+ const Comp = {
+ render() {
+ return h(Child, () => {
+ return h('div', { ref: 'foo' })
+ })
+ },
+ mounted(this: any) {
+ spy(this.$refs.foo.tag)
+ }
+ }
+ render(h(Comp), root)
+
+ expect(spy).toHaveBeenCalledWith('div')
+ })
})
VNodeArrayChildren,
createVNode,
isSameVNodeType,
- Static
+ Static,
+ VNodeNormalizedRef
} from './vnode'
import {
ComponentInternalInstance,
isFunction,
PatchFlags,
ShapeFlags,
- NOOP,
- isArray
+ NOOP
} from '@vue/shared'
import {
queueJob,
stop,
ReactiveEffectOptions,
isRef,
- Ref,
toRaw,
DebuggerEvent
} from '@vue/reactivity'
}
const setRef = (
- ref: string | Function | Ref | [ComponentPublicInstance, string],
- oldRef: string | Function | Ref | [ComponentPublicInstance, string] | null,
+ rawRef: VNodeNormalizedRef,
+ oldRawRef: VNodeNormalizedRef | null,
parent: ComponentInternalInstance,
value: HostNode | ComponentPublicInstance | null
) => {
- if (isArray(ref)) {
- // template string refs are compiled into tuples like [ctx, key] to
- // ensure refs inside slots are set on the correct owner instance.
- const [{ $: owner }, key] = ref
- setRef(key, oldRef && (oldRef as any[])[1], owner, value)
+ const [owner, ref] = rawRef
+ if (__DEV__ && !owner) {
+ warn(
+ `Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
+ `A vnode with ref must be created inside the render function.`
+ )
return
}
-
- const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
- const renderContext = toRaw(parent.renderContext)
+ const oldRef = oldRawRef && oldRawRef[1]
+ const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
+ const renderContext = toRaw(owner.renderContext)
// unset old ref
if (oldRef !== null && oldRef !== ref) {
} else if (isRef(ref)) {
ref.value = value
} else if (isFunction(ref)) {
- callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value])
+ callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
} else if (__DEV__) {
warn('Invalid template ref type:', value, `(${typeof value})`)
}
| typeof PortalImpl
| typeof SuspenseImpl
+export type VNodeRef =
+ | string
+ | Ref
+ | ((ref: object | null, refs: Record<string, any>) => void)
+
+export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
+
export interface VNodeProps {
[key: string]: any
key?: string | number
- ref?: string | Ref | ((ref: object | null) => void)
+ ref?: VNodeRef
// vnode hooks
onVnodeBeforeMount?: (vnode: VNode) => void
type: VNodeTypes
props: VNodeProps | null
key: string | number | null
- ref: string | Ref | ((ref: object | null) => void) | null
+ ref: VNodeNormalizedRef | null
scopeId: string | null // SFC only
children: VNodeNormalizedChildren<HostNode, HostElement>
component: ComponentInternalInstance | null
type,
props,
key: props !== null && props.key !== undefined ? props.key : null,
- ref: (props !== null && props.ref) || null,
+ ref:
+ props !== null && props.ref !== undefined
+ ? [currentRenderingInstance!, props.ref]
+ : null,
scopeId: currentScopeId,
children: null,
component: null,