Skip to content

Commit

Permalink
feat: useTemplateRef()
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jul 17, 2024
1 parent 0ae7316 commit 3ba70e4
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
8 changes: 8 additions & 0 deletions packages/dts-test/ref.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
toRefs,
toValue,
unref,
useTemplateRef,
} from 'vue'
import { type IsAny, type IsUnion, describe, expectType } from './utils'

Expand Down Expand Up @@ -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)
71 changes: 71 additions & 0 deletions packages/runtime-core/__tests__/apiTemplateRef.spec.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions packages/runtime-core/src/apiTemplateRef.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ----------------------------------------------------------

Expand Down

0 comments on commit 3ba70e4

Please sign in to comment.