Skip to content

Commit 3eab143

Browse files
committed
fix(template-ref): fix string template refs inside slots
1 parent 8cb0b83 commit 3eab143

File tree

6 files changed

+110
-7
lines changed

6 files changed

+110
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { baseParse as parse } from '../../src/parse'
2+
import { transform } from '../../src/transform'
3+
import { transformRef } from '../../src/transforms/transformRef'
4+
import { ElementNode, NodeTypes } from '../../src/ast'
5+
6+
function transformWithRef(template: string) {
7+
const ast = parse(template)
8+
transform(ast, {
9+
nodeTransforms: [transformRef]
10+
})
11+
return ast.children[0] as ElementNode
12+
}
13+
14+
describe('compiler: transform ref', () => {
15+
const getExpected = (key: any) => ({
16+
type: NodeTypes.DIRECTIVE,
17+
name: 'bind',
18+
arg: {
19+
type: NodeTypes.SIMPLE_EXPRESSION,
20+
content: `ref`
21+
},
22+
exp: {
23+
type: NodeTypes.COMPOUND_EXPRESSION,
24+
children: [`[_ctx, `, key, `]`]
25+
}
26+
})
27+
28+
test('static', () => {
29+
const node = transformWithRef(`<div ref="test"/>`)
30+
expect(node.props[0]).toMatchObject(
31+
getExpected({
32+
type: NodeTypes.SIMPLE_EXPRESSION,
33+
content: `test`,
34+
isStatic: true
35+
})
36+
)
37+
})
38+
39+
test('dynamic', () => {
40+
const node = transformWithRef(`<div :ref="test"/>`)
41+
expect(node.props[0]).toMatchObject(
42+
getExpected({
43+
type: NodeTypes.SIMPLE_EXPRESSION,
44+
content: `test`,
45+
isStatic: false
46+
})
47+
)
48+
})
49+
})

packages/compiler-core/src/compile.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { transform, NodeTransform, DirectiveTransform } from './transform'
44
import { generate, CodegenResult } from './codegen'
55
import { RootNode } from './ast'
66
import { isString } from '@vue/shared'
7+
import { transformRef } from './transforms/transformRef'
78
import { transformIf } from './transforms/vIf'
89
import { transformFor } from './transforms/vFor'
910
import { transformExpression } from './transforms/transformExpression'
@@ -27,6 +28,7 @@ export function getBaseTransformPreset(
2728
): TransformPreset {
2829
return [
2930
[
31+
transformRef,
3032
transformOnce,
3133
transformIf,
3234
transformFor,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { NodeTransform } from '../transform'
2+
import {
3+
NodeTypes,
4+
ElementTypes,
5+
createSimpleExpression,
6+
createCompoundExpression
7+
} from '../ast'
8+
import { findProp } from '../utils'
9+
10+
// Convert ref="foo" to `:ref="[_ctx, 'foo']"` so that the ref contains the
11+
// correct owner instance even inside slots.
12+
export const transformRef: NodeTransform = node => {
13+
if (
14+
!(
15+
node.type === NodeTypes.ELEMENT &&
16+
(node.tagType === ElementTypes.ELEMENT ||
17+
node.tagType === ElementTypes.COMPONENT)
18+
)
19+
) {
20+
return
21+
}
22+
const ref = findProp(node, 'ref')
23+
if (!ref) return
24+
const refKey =
25+
ref.type === NodeTypes.ATTRIBUTE
26+
? ref.value
27+
? createSimpleExpression(ref.value.content, true, ref.value.loc)
28+
: null
29+
: ref.exp
30+
if (refKey) {
31+
node.props[node.props.indexOf(ref)] = {
32+
type: NodeTypes.DIRECTIVE,
33+
name: `bind`,
34+
arg: createSimpleExpression(`ref`, true, ref.loc),
35+
exp: createCompoundExpression([`[_ctx, `, refKey, `]`]),
36+
modifiers: [],
37+
loc: ref.loc
38+
}
39+
}
40+
}

packages/runtime-core/__tests__/apiTemplateRef.spec.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ describe('api: template refs', () => {
2222
}
2323
},
2424
render() {
25-
return h('div', { ref: 'refKey' })
25+
// Note: string refs are compiled into [ctx, key] tuples by the compiler
26+
// to ensure correct context.
27+
return h('div', { ref: [this, 'refKey'] as any })
2628
}
2729
}
2830
render(h(Comp), root)
@@ -43,7 +45,7 @@ describe('api: template refs', () => {
4345
}
4446
},
4547
render() {
46-
return h('div', { ref: refKey.value })
48+
return h('div', { ref: [this, refKey.value] as any })
4749
}
4850
}
4951
render(h(Comp), root)
@@ -68,7 +70,7 @@ describe('api: template refs', () => {
6870
}
6971
},
7072
render() {
71-
return toggle.value ? h('div', { ref: 'refKey' }) : null
73+
return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null
7274
}
7375
}
7476
render(h(Comp), root)

packages/runtime-core/src/componentProxy.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type ComponentPublicInstance<
3333
M extends MethodOptions = {},
3434
PublicProps = P
3535
> = {
36+
$: ComponentInternalInstance
3637
$data: D
3738
$props: PublicProps
3839
$attrs: Data

packages/runtime-core/src/renderer.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import {
3030
isFunction,
3131
PatchFlags,
3232
ShapeFlags,
33-
NOOP
33+
NOOP,
34+
isArray
3435
} from '@vue/shared'
3536
import {
3637
queueJob,
@@ -1793,11 +1794,19 @@ function baseCreateRenderer<
17931794
}
17941795

17951796
const setRef = (
1796-
ref: string | Function | Ref,
1797-
oldRef: string | Function | Ref | null,
1797+
ref: string | Function | Ref | [ComponentPublicInstance, string],
1798+
oldRef: string | Function | Ref | [ComponentPublicInstance, string] | null,
17981799
parent: ComponentInternalInstance,
17991800
value: HostNode | ComponentPublicInstance | null
18001801
) => {
1802+
if (isArray(ref)) {
1803+
// template string refs are compiled into tuples like [ctx, key] to
1804+
// ensure refs inside slots are set on the correct owner instance.
1805+
const [{ $: owner }, key] = ref
1806+
setRef(key, oldRef && (oldRef as any[])[1], owner, value)
1807+
return
1808+
}
1809+
18011810
const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
18021811
const renderContext = toRaw(parent.renderContext)
18031812

@@ -1823,7 +1832,7 @@ function baseCreateRenderer<
18231832
} else if (isRef(ref)) {
18241833
ref.value = value
18251834
} else if (isFunction(ref)) {
1826-
callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
1835+
callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value])
18271836
} else if (__DEV__) {
18281837
warn('Invalid template ref type:', value, `(${typeof value})`)
18291838
}

0 commit comments

Comments
 (0)