Skip to content

Commit

Permalink
fix(template-ref): fix string template refs inside slots
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 25, 2020
1 parent 8cb0b83 commit 3eab143
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 7 deletions.
49 changes: 49 additions & 0 deletions packages/compiler-core/__tests__/transforms/transformRef.spec.ts
Original file line number Diff line number Diff line change
@@ -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(`<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
})
)
})
})
2 changes: 2 additions & 0 deletions packages/compiler-core/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -27,6 +28,7 @@ export function getBaseTransformPreset(
): TransformPreset {
return [
[
transformRef,
transformOnce,
transformIf,
transformFor,
Expand Down
40 changes: 40 additions & 0 deletions packages/compiler-core/src/transforms/transformRef.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
}
8 changes: 5 additions & 3 deletions packages/runtime-core/__tests__/apiTemplateRef.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-core/src/componentProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type ComponentPublicInstance<
M extends MethodOptions = {},
PublicProps = P
> = {
$: ComponentInternalInstance
$data: D
$props: PublicProps
$attrs: Data
Expand Down
17 changes: 13 additions & 4 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
isFunction,
PatchFlags,
ShapeFlags,
NOOP
NOOP,
isArray
} from '@vue/shared'
import {
queueJob,
Expand Down Expand Up @@ -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)

Expand All @@ -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})`)
}
Expand Down

0 comments on commit 3eab143

Please sign in to comment.