فهرست منبع

feat: useTemplateRef()

Evan You 1 سال پیش
والد
کامیت
3ba70e49b5

+ 8 - 0
packages/dts-test/ref.test-d.ts

@@ -17,6 +17,7 @@ import {
   toRefs,
   toRefs,
   toValue,
   toValue,
   unref,
   unref,
+  useTemplateRef,
 } from 'vue'
 } from 'vue'
 import { type IsAny, type IsUnion, describe, expectType } from './utils'
 import { type IsAny, type IsUnion, describe, expectType } from './utils'
 
 
@@ -456,3 +457,10 @@ describe('toRef <-> toValue', () => {
 // unref
 // unref
 declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
 declare const text: ShallowRef<string> | ComputedRef<string> | MaybeRef<string>
 expectType<string>(unref(text))
 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)

+ 71 - 0
packages/runtime-core/__tests__/apiTemplateRef.spec.ts

@@ -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)
+  })
+})

+ 25 - 0
packages/runtime-core/src/apiTemplateRef.ts

@@ -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
+}

+ 1 - 0
packages/runtime-core/src/index.ts

@@ -62,6 +62,7 @@ export { defineComponent } from './apiDefineComponent'
 export { defineAsyncComponent } from './apiAsyncComponent'
 export { defineAsyncComponent } from './apiAsyncComponent'
 export { useAttrs, useSlots } from './apiSetupHelpers'
 export { useAttrs, useSlots } from './apiSetupHelpers'
 export { useModel } from './helpers/useModel'
 export { useModel } from './helpers/useModel'
+export { useTemplateRef } from './apiTemplateRef'
 
 
 // <script setup> API ----------------------------------------------------------
 // <script setup> API ----------------------------------------------------------