]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: useTemplateRef()
authorEvan You <evan@vuejs.org>
Wed, 17 Jul 2024 10:20:23 +0000 (18:20 +0800)
committerEvan You <evan@vuejs.org>
Wed, 17 Jul 2024 10:20:23 +0000 (18:20 +0800)
packages/dts-test/ref.test-d.ts
packages/runtime-core/__tests__/apiTemplateRef.spec.ts [new file with mode: 0644]
packages/runtime-core/src/apiTemplateRef.ts [new file with mode: 0644]
packages/runtime-core/src/index.ts

index 1456c523239bda8a991ac4683d72ee28f032b610..9de9f3f0aa93e474136492d9890936c38b849589 100644 (file)
@@ -17,6 +17,7 @@ import {
   toRefs,
   toValue,
   unref,
+  useTemplateRef,
 } from 'vue'
 import { type IsAny, type IsUnion, describe, expectType } from './utils'
 
@@ -456,3 +457,10 @@ describe('toRef <-> toValue', () => {
 // 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)
diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
new file mode 100644 (file)
index 0000000..fa69adc
--- /dev/null
@@ -0,0 +1,71 @@
+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)
+  })
+})
diff --git a/packages/runtime-core/src/apiTemplateRef.ts b/packages/runtime-core/src/apiTemplateRef.ts
new file mode 100644 (file)
index 0000000..df26514
--- /dev/null
@@ -0,0 +1,25 @@
+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
+}
index d5f0e6865c289b040e1802bd5128e333fbd4b2ce..dd8c4eefa87b9b24d32aa85d5d4757fe8e331746 100644 (file)
@@ -62,6 +62,7 @@ export { defineComponent } from './apiDefineComponent'
 export { defineAsyncComponent } from './apiAsyncComponent'
 export { useAttrs, useSlots } from './apiSetupHelpers'
 export { useModel } from './helpers/useModel'
+export { useTemplateRef } from './apiTemplateRef'
 
 // <script setup> API ----------------------------------------------------------