--- /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() {
- 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)
}
},
render() {
- return h('div', { ref: refKey.value })
+ return h('div', { ref: [this, refKey.value] as any })
}
}
render(h(Comp), root)
}
},
render() {
- return toggle.value ? h('div', { ref: 'refKey' }) : null
+ return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null
}
}
render(h(Comp), root)
M extends MethodOptions = {},
PublicProps = P
> = {
+ $: ComponentInternalInstance
$data: D
$props: PublicProps
$attrs: Data
isFunction,
PatchFlags,
ShapeFlags,
- NOOP
+ NOOP,
+ isArray
} from '@vue/shared'
import {
queueJob,
}
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)
} 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})`)
}