]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(template-ref): fix string template refs inside slots
authorEvan You <yyx990803@gmail.com>
Tue, 25 Feb 2020 23:29:51 +0000 (18:29 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 25 Feb 2020 23:29:51 +0000 (18:29 -0500)
packages/compiler-core/__tests__/transforms/transformRef.spec.ts [new file with mode: 0644]
packages/compiler-core/src/compile.ts
packages/compiler-core/src/transforms/transformRef.ts [new file with mode: 0644]
packages/runtime-core/__tests__/apiTemplateRef.spec.ts
packages/runtime-core/src/componentProxy.ts
packages/runtime-core/src/renderer.ts

diff --git a/packages/compiler-core/__tests__/transforms/transformRef.spec.ts b/packages/compiler-core/__tests__/transforms/transformRef.spec.ts
new file mode 100644 (file)
index 0000000..8bff339
--- /dev/null
@@ -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
+      })
+    )
+  })
+})
index f7717228a45d04f70858be027ec6ab80b271b4d7..78ed33c6daaf869a92699c8bae94353531b08705 100644 (file)
@@ -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 (file)
index 0000000..0c2aa2a
--- /dev/null
@@ -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
+    }
+  }
+}
index 68b14dd2b28673bb02ac2fb3e0dcc750dc4c74f9..7fe111a5818af5d6b435244fbb0b167761f3d56a 100644 (file)
@@ -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)
index a69d33da140fe7e9919f79cdf302e7e7b41e83cb..46fe10d2d2f418df7e94e8a78617685cd21d290e 100644 (file)
@@ -33,6 +33,7 @@ export type ComponentPublicInstance<
   M extends MethodOptions = {},
   PublicProps = P
 > = {
+  $: ComponentInternalInstance
   $data: D
   $props: PublicProps
   $attrs: Data
index ab5b58cecf234da4f0a7a53c473b2956a257db94..a92708633ca9a60b56ebec29a5a9012a2b0a6364 100644 (file)
@@ -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})`)
     }