From: Evan You Date: Tue, 25 Feb 2020 23:29:51 +0000 (-0500) Subject: fix(template-ref): fix string template refs inside slots X-Git-Tag: v3.0.0-alpha.7~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3eab1438432a3bab15ccf2f6092fc3e4355f3cdd;p=thirdparty%2Fvuejs%2Fcore.git fix(template-ref): fix string template refs inside slots --- diff --git a/packages/compiler-core/__tests__/transforms/transformRef.spec.ts b/packages/compiler-core/__tests__/transforms/transformRef.spec.ts new file mode 100644 index 0000000000..8bff3398ec --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/transformRef.spec.ts @@ -0,0 +1,49 @@ +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 f7717228a4..78ed33c6da 100644 --- a/packages/compiler-core/src/compile.ts +++ b/packages/compiler-core/src/compile.ts @@ -4,6 +4,7 @@ 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' @@ -27,6 +28,7 @@ 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 new file mode 100644 index 0000000000..0c2aa2a59b --- /dev/null +++ b/packages/compiler-core/src/transforms/transformRef.ts @@ -0,0 +1,40 @@ +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 68b14dd2b2..7fe111a581 100644 --- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts @@ -22,7 +22,9 @@ describe('api: template refs', () => { } }, render() { - return h('div', { ref: 'refKey' }) + // Note: string refs are compiled into [ctx, key] tuples by the compiler + // to ensure correct context. + return h('div', { ref: [this, 'refKey'] as any }) } } render(h(Comp), root) @@ -43,7 +45,7 @@ describe('api: template refs', () => { } }, render() { - return h('div', { ref: refKey.value }) + return h('div', { ref: [this, refKey.value] as any }) } } render(h(Comp), root) @@ -68,7 +70,7 @@ describe('api: template refs', () => { } }, render() { - return toggle.value ? h('div', { ref: 'refKey' }) : null + return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null } } render(h(Comp), root) diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index a69d33da14..46fe10d2d2 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -33,6 +33,7 @@ export type ComponentPublicInstance< M extends MethodOptions = {}, PublicProps = P > = { + $: ComponentInternalInstance $data: D $props: PublicProps $attrs: Data diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index ab5b58cecf..a92708633c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -30,7 +30,8 @@ import { isFunction, PatchFlags, ShapeFlags, - NOOP + NOOP, + isArray } from '@vue/shared' import { queueJob, @@ -1793,11 +1794,19 @@ function baseCreateRenderer< } const setRef = ( - ref: string | Function | Ref, - oldRef: string | Function | Ref | null, + ref: string | Function | Ref | [ComponentPublicInstance, string], + oldRef: string | Function | Ref | [ComponentPublicInstance, string] | 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) + return + } + const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs const renderContext = toRaw(parent.renderContext) @@ -1823,7 +1832,7 @@ function baseCreateRenderer< } else if (isRef(ref)) { ref.value = value } else if (isFunction(ref)) { - callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs]) + callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value]) } else if (__DEV__) { warn('Invalid template ref type:', value, `(${typeof value})`) }