fix(template-ref): fix string template refs inside slots
This commit is contained in:
		
							parent
							
								
									8cb0b83088
								
							
						
					
					
						commit
						3eab143843
					
				@ -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
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@ -4,6 +4,7 @@ import { transform, NodeTransform, DirectiveTransform } from './transform'
 | 
				
			|||||||
import { generate, CodegenResult } from './codegen'
 | 
					import { generate, CodegenResult } from './codegen'
 | 
				
			||||||
import { RootNode } from './ast'
 | 
					import { RootNode } from './ast'
 | 
				
			||||||
import { isString } from '@vue/shared'
 | 
					import { isString } from '@vue/shared'
 | 
				
			||||||
 | 
					import { transformRef } from './transforms/transformRef'
 | 
				
			||||||
import { transformIf } from './transforms/vIf'
 | 
					import { transformIf } from './transforms/vIf'
 | 
				
			||||||
import { transformFor } from './transforms/vFor'
 | 
					import { transformFor } from './transforms/vFor'
 | 
				
			||||||
import { transformExpression } from './transforms/transformExpression'
 | 
					import { transformExpression } from './transforms/transformExpression'
 | 
				
			||||||
@ -27,6 +28,7 @@ export function getBaseTransformPreset(
 | 
				
			|||||||
): TransformPreset {
 | 
					): TransformPreset {
 | 
				
			||||||
  return [
 | 
					  return [
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
 | 
					      transformRef,
 | 
				
			||||||
      transformOnce,
 | 
					      transformOnce,
 | 
				
			||||||
      transformIf,
 | 
					      transformIf,
 | 
				
			||||||
      transformFor,
 | 
					      transformFor,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										40
									
								
								packages/compiler-core/src/transforms/transformRef.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/compiler-core/src/transforms/transformRef.ts
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,7 +22,9 @@ describe('api: template refs', () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render() {
 | 
					      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(h(Comp), root)
 | 
				
			||||||
@ -43,7 +45,7 @@ describe('api: template refs', () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render() {
 | 
					      render() {
 | 
				
			||||||
        return h('div', { ref: refKey.value })
 | 
					        return h('div', { ref: [this, refKey.value] as any })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    render(h(Comp), root)
 | 
					    render(h(Comp), root)
 | 
				
			||||||
@ -68,7 +70,7 @@ describe('api: template refs', () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      render() {
 | 
					      render() {
 | 
				
			||||||
        return toggle.value ? h('div', { ref: 'refKey' }) : null
 | 
					        return toggle.value ? h('div', { ref: [this, 'refKey'] as any }) : null
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    render(h(Comp), root)
 | 
					    render(h(Comp), root)
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@ export type ComponentPublicInstance<
 | 
				
			|||||||
  M extends MethodOptions = {},
 | 
					  M extends MethodOptions = {},
 | 
				
			||||||
  PublicProps = P
 | 
					  PublicProps = P
 | 
				
			||||||
> = {
 | 
					> = {
 | 
				
			||||||
 | 
					  $: ComponentInternalInstance
 | 
				
			||||||
  $data: D
 | 
					  $data: D
 | 
				
			||||||
  $props: PublicProps
 | 
					  $props: PublicProps
 | 
				
			||||||
  $attrs: Data
 | 
					  $attrs: Data
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,8 @@ import {
 | 
				
			|||||||
  isFunction,
 | 
					  isFunction,
 | 
				
			||||||
  PatchFlags,
 | 
					  PatchFlags,
 | 
				
			||||||
  ShapeFlags,
 | 
					  ShapeFlags,
 | 
				
			||||||
  NOOP
 | 
					  NOOP,
 | 
				
			||||||
 | 
					  isArray
 | 
				
			||||||
} from '@vue/shared'
 | 
					} from '@vue/shared'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  queueJob,
 | 
					  queueJob,
 | 
				
			||||||
@ -1793,11 +1794,19 @@ function baseCreateRenderer<
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const setRef = (
 | 
					  const setRef = (
 | 
				
			||||||
    ref: string | Function | Ref,
 | 
					    ref: string | Function | Ref | [ComponentPublicInstance, string],
 | 
				
			||||||
    oldRef: string | Function | Ref | null,
 | 
					    oldRef: string | Function | Ref | [ComponentPublicInstance, string] | null,
 | 
				
			||||||
    parent: ComponentInternalInstance,
 | 
					    parent: ComponentInternalInstance,
 | 
				
			||||||
    value: HostNode | ComponentPublicInstance | null
 | 
					    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 refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
 | 
				
			||||||
    const renderContext = toRaw(parent.renderContext)
 | 
					    const renderContext = toRaw(parent.renderContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1823,7 +1832,7 @@ function baseCreateRenderer<
 | 
				
			|||||||
    } else if (isRef(ref)) {
 | 
					    } else if (isRef(ref)) {
 | 
				
			||||||
      ref.value = value
 | 
					      ref.value = value
 | 
				
			||||||
    } else if (isFunction(ref)) {
 | 
					    } else if (isFunction(ref)) {
 | 
				
			||||||
      callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
 | 
					      callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value])
 | 
				
			||||||
    } else if (__DEV__) {
 | 
					    } else if (__DEV__) {
 | 
				
			||||||
      warn('Invalid template ref type:', value, `(${typeof value})`)
 | 
					      warn('Invalid template ref type:', value, `(${typeof value})`)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user