]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(template-ref): improve template ref handling
authorEvan You <yyx990803@gmail.com>
Mon, 16 Mar 2020 16:40:58 +0000 (12:40 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 16 Mar 2020 17:06:46 +0000 (13:06 -0400)
close #836, close #839

packages/compiler-core/__tests__/transforms/transformRef.spec.ts [deleted file]
packages/compiler-core/src/compile.ts
packages/compiler-core/src/transforms/transformRef.ts [deleted file]
packages/runtime-core/__tests__/apiTemplateRef.spec.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts

diff --git a/packages/compiler-core/__tests__/transforms/transformRef.spec.ts b/packages/compiler-core/__tests__/transforms/transformRef.spec.ts
deleted file mode 100644 (file)
index 8bff339..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-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 78ed33c6daaf869a92699c8bae94353531b08705..f7717228a45d04f70858be027ec6ab80b271b4d7 100644 (file)
@@ -4,7 +4,6 @@ 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'
@@ -28,7 +27,6 @@ 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
deleted file mode 100644 (file)
index 0c2aa2a..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-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 7fe111a5818af5d6b435244fbb0b167761f3d56a..88224eba1a8a59dbbf891b8fb2e07176d8cd9129 100644 (file)
@@ -22,9 +22,7 @@ describe('api: template refs', () => {
         }
       },
       render() {
-        // Note: string refs are compiled into [ctx, key] tuples by the compiler
-        // to ensure correct context.
-        return h('div', { ref: [this, 'refKey'] as any })
+        return h('div', { ref: 'refKey' })
       }
     }
     render(h(Comp), root)
@@ -45,7 +43,7 @@ describe('api: template refs', () => {
         }
       },
       render() {
-        return h('div', { ref: [this, refKey.value] as any })
+        return h('div', { ref: refKey.value })
       }
     }
     render(h(Comp), root)
@@ -70,7 +68,7 @@ describe('api: template refs', () => {
         }
       },
       render() {
-        return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null
+        return toggle.value ? h('div', { ref: 'refKey' }) : null
       }
     }
     render(h(Comp), root)
@@ -178,4 +176,28 @@ describe('api: template refs', () => {
     await nextTick()
     expect(el.value).toBe(null)
   })
+
+  test('string ref inside slots', async () => {
+    const root = nodeOps.createElement('div')
+    const spy = jest.fn()
+    const Child = {
+      render(this: any) {
+        return this.$slots.default()
+      }
+    }
+
+    const Comp = {
+      render() {
+        return h(Child, () => {
+          return h('div', { ref: 'foo' })
+        })
+      },
+      mounted(this: any) {
+        spy(this.$refs.foo.tag)
+      }
+    }
+    render(h(Comp), root)
+
+    expect(spy).toHaveBeenCalledWith('div')
+  })
 })
index f3d5fcbea57d8885ac40f6a143a2f0c1893d9cba..1f1b0ffeb50c4c172a0c43b9e0c7071afcd0e886 100644 (file)
@@ -8,7 +8,8 @@ import {
   VNodeArrayChildren,
   createVNode,
   isSameVNodeType,
-  Static
+  Static,
+  VNodeNormalizedRef
 } from './vnode'
 import {
   ComponentInternalInstance,
@@ -30,8 +31,7 @@ import {
   isFunction,
   PatchFlags,
   ShapeFlags,
-  NOOP,
-  isArray
+  NOOP
 } from '@vue/shared'
 import {
   queueJob,
@@ -44,7 +44,6 @@ import {
   stop,
   ReactiveEffectOptions,
   isRef,
-  Ref,
   toRaw,
   DebuggerEvent
 } from '@vue/reactivity'
@@ -1789,21 +1788,22 @@ function baseCreateRenderer<
   }
 
   const setRef = (
-    ref: string | Function | Ref | [ComponentPublicInstance, string],
-    oldRef: string | Function | Ref | [ComponentPublicInstance, string] | null,
+    rawRef: VNodeNormalizedRef,
+    oldRawRef: VNodeNormalizedRef | 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)
+    const [owner, ref] = rawRef
+    if (__DEV__ && !owner) {
+      warn(
+        `Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
+          `A vnode with ref must be created inside the render function.`
+      )
       return
     }
-
-    const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
-    const renderContext = toRaw(parent.renderContext)
+    const oldRef = oldRawRef && oldRawRef[1]
+    const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
+    const renderContext = toRaw(owner.renderContext)
 
     // unset old ref
     if (oldRef !== null && oldRef !== ref) {
@@ -1827,7 +1827,7 @@ function baseCreateRenderer<
     } else if (isRef(ref)) {
       ref.value = value
     } else if (isFunction(ref)) {
-      callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value])
+      callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
     } else if (__DEV__) {
       warn('Invalid template ref type:', value, `(${typeof value})`)
     }
index ce91483c8cba5421f26e8ae574e143e6b574f38d..d8dfb64665595c2f6277ed8332049986b27f7651 100644 (file)
@@ -52,10 +52,17 @@ export type VNodeTypes =
   | typeof PortalImpl
   | typeof SuspenseImpl
 
+export type VNodeRef =
+  | string
+  | Ref
+  | ((ref: object | null, refs: Record<string, any>) => void)
+
+export type VNodeNormalizedRef = [ComponentInternalInstance, VNodeRef]
+
 export interface VNodeProps {
   [key: string]: any
   key?: string | number
-  ref?: string | Ref | ((ref: object | null) => void)
+  ref?: VNodeRef
 
   // vnode hooks
   onVnodeBeforeMount?: (vnode: VNode) => void
@@ -95,7 +102,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   type: VNodeTypes
   props: VNodeProps | null
   key: string | number | null
-  ref: string | Ref | ((ref: object | null) => void) | null
+  ref: VNodeNormalizedRef | null
   scopeId: string | null // SFC only
   children: VNodeNormalizedChildren<HostNode, HostElement>
   component: ComponentInternalInstance | null
@@ -261,7 +268,10 @@ export function createVNode(
     type,
     props,
     key: props !== null && props.key !== undefined ? props.key : null,
-    ref: (props !== null && props.ref) || null,
+    ref:
+      props !== null && props.ref !== undefined
+        ? [currentRenderingInstance!, props.ref]
+        : null,
     scopeId: currentScopeId,
     children: null,
     component: null,