toRefs,
toValue,
unref,
+ useTemplateRef,
} from 'vue'
import { type IsAny, type IsUnion, describe, expectType } from './utils'
// unref
declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
expectType<string>(unref(text))
+
+// useTemplateRef
+const tRef = useTemplateRef('foo')
+expectType<Readonly<ShallowRef<unknown>>>(tRef)
+
+const tRef2 = useTemplateRef<HTMLElement>('bar')
+expectType<Readonly<ShallowRef<HTMLElement | null>>>(tRef2)
--- /dev/null
+import {
+ h,
+ nextTick,
+ nodeOps,
+ ref,
+ render,
+ useTemplateRef,
+} from '@vue/runtime-test'
+
+describe('useTemplateRef', () => {
+ test('should work', () => {
+ let tRef
+ const key = 'refKey'
+ const Comp = {
+ setup() {
+ tRef = useTemplateRef(key)
+ },
+ render() {
+ return h('div', { ref: key })
+ },
+ }
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ expect(tRef!.value).toBe(root.children[0])
+ })
+
+ test('should be readonly', () => {
+ let tRef
+ const key = 'refKey'
+ const Comp = {
+ setup() {
+ tRef = useTemplateRef(key)
+ },
+ render() {
+ return h('div', { ref: key })
+ },
+ }
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+
+ // @ts-expect-error
+ tRef.value = 123
+
+ expect(tRef!.value).toBe(root.children[0])
+ expect('target is readonly').toHaveBeenWarned()
+ })
+
+ test('should be updated for ref of dynamic strings', async () => {
+ let t1, t2
+ const key = ref('t1')
+ const Comp = {
+ setup() {
+ t1 = useTemplateRef<HTMLAnchorElement>('t1')
+ t2 = useTemplateRef('t2')
+ },
+ render() {
+ return h('div', { ref: key.value })
+ },
+ }
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+
+ expect(t1!.value).toBe(root.children[0])
+ expect(t2!.value).toBe(null)
+
+ key.value = 't2'
+ await nextTick()
+ expect(t2!.value).toBe(root.children[0])
+ expect(t1!.value).toBe(null)
+ })
+})
--- /dev/null
+import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
+import { getCurrentInstance } from './component'
+import { warn } from './warning'
+import { EMPTY_OBJ } from '@vue/shared'
+
+export function useTemplateRef<T = unknown>(
+ key: string,
+): Readonly<ShallowRef<T | null>> {
+ const i = getCurrentInstance()
+ const r = shallowRef(null)
+ if (i) {
+ const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
+ Object.defineProperty(refs, key, {
+ enumerable: true,
+ get: () => r.value,
+ set: val => (r.value = val),
+ })
+ } else if (__DEV__) {
+ warn(
+ `useTemplateRef() is called when there is no active component ` +
+ `instance to be associated with.`,
+ )
+ }
+ return (__DEV__ ? readonly(r) : r) as any
+}
export { defineAsyncComponent } from './apiAsyncComponent'
export { useAttrs, useSlots } from './apiSetupHelpers'
export { useModel } from './helpers/useModel'
+export { useTemplateRef } from './apiTemplateRef'
// <script setup> API ----------------------------------------------------------