diff --git a/packages/compiler-core/__tests__/transforms/transformRef.spec.ts b/packages/compiler-core/__tests__/transforms/transformRef.spec.ts deleted file mode 100644 index 8bff3398..00000000 --- a/packages/compiler-core/__tests__/transforms/transformRef.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -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(`
`) - expect(node.props[0]).toMatchObject( - getExpected({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `test`, - isStatic: true - }) - ) - }) - - test('dynamic', () => { - const node = transformWithRef(`
`) - expect(node.props[0]).toMatchObject( - getExpected({ - type: NodeTypes.SIMPLE_EXPRESSION, - content: `test`, - isStatic: false - }) - ) - }) -}) diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts index 78ed33c6..f7717228 100644 --- a/packages/compiler-core/src/compile.ts +++ b/packages/compiler-core/src/compile.ts @@ -4,7 +4,6 @@ import { transform, NodeTransform, DirectiveTransform } from './transform' 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' @@ -28,7 +27,6 @@ export function getBaseTransformPreset( ): TransformPreset { return [ [ - transformRef, transformOnce, transformIf, transformFor, diff --git a/packages/compiler-core/src/transforms/transformRef.ts b/packages/compiler-core/src/transforms/transformRef.ts deleted file mode 100644 index 0c2aa2a5..00000000 --- a/packages/compiler-core/src/transforms/transformRef.ts +++ /dev/null @@ -1,40 +0,0 @@ -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 - } - } -} diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts index 7fe111a5..88224eba 100644 --- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts @@ -22,9 +22,7 @@ describe('api: template refs', () => { } }, 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) @@ -45,7 +43,7 @@ describe('api: template refs', () => { } }, render() { - return h('div', { ref: [this, refKey.value] as any }) + return h('div', { ref: refKey.value }) } } render(h(Comp), root) @@ -70,7 +68,7 @@ describe('api: template refs', () => { } }, render() { - return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null + return toggle.value ? h('div', { ref: 'refKey' }) : null } } render(h(Comp), root) @@ -178,4 +176,28 @@ describe('api: template refs', () => { 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') + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index f3d5fcbe..1f1b0ffe 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -8,7 +8,8 @@ import { VNodeArrayChildren, createVNode, isSameVNodeType, - Static + Static, + VNodeNormalizedRef } from './vnode' import { ComponentInternalInstance, @@ -30,8 +31,7 @@ import { isFunction, PatchFlags, ShapeFlags, - NOOP, - isArray + NOOP } from '@vue/shared' import { queueJob, @@ -44,7 +44,6 @@ import { stop, ReactiveEffectOptions, isRef, - Ref, toRaw, DebuggerEvent } from '@vue/reactivity' @@ -1789,21 +1788,22 @@ function baseCreateRenderer< } 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) { @@ -1827,7 +1827,7 @@ function baseCreateRenderer< } 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})`) } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index ce91483c..d8dfb646 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -52,10 +52,17 @@ export type VNodeTypes = | typeof PortalImpl | typeof SuspenseImpl +export type VNodeRef = + | string + | Ref + | ((ref: object | null, refs: Record) => 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 @@ -95,7 +102,7 @@ export interface VNode { 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 component: ComponentInternalInstance | null @@ -261,7 +268,10 @@ export function createVNode( 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,