组件引用
在 Vitarx 中,你可以通过 ref 属性获取 DOM 元素或组件实例的引用。对于组件,还可以通过 defineExpose 选择性地暴露内部成员给外部使用。
useRef() — 创建引用
useRef 创建一个响应式引用,初始值为 null。当绑定到元素或组件后,引用的 .value 就会指向对应的实例。
typescript
declare function useRef<T>(): ShallowRef<T | null>- 泛型
T传入 DOM 元素类型时,.value为对应的 DOM 元素 - 泛型
T传入组件类型时,.value为组件的公开实例
tsx
import { useRef } from 'vitarx'
function App() {
// 创建 DOM 元素引用
const divRef = useRef<HTMLDivElement>()
// 创建组件实例引用
const childRef = useRef()
return (
<div>
<div ref={divRef}>Hello</div>
<Child ref={childRef} />
</div>
)
}ref 绑定 DOM 元素
将 ref 绑定到 HTML 元素上,可以在挂载后通过 .value 访问原生 DOM 元素:
tsx
import { useRef, onMounted } from 'vitarx'
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>()
onMounted(() => {
// 挂载后让输入框自动获取焦点
inputRef.value?.focus()
})
return <input ref={inputRef} placeholder="自动聚焦" />
}ref 绑定组件实例
将 ref 绑定到组件标签上,可以在挂载后通过 .value 访问组件的公开实例:
tsx
import { useRef, onMounted, defineExpose, ref } from 'vitarx'
// 子组件
function Counter() {
const count = ref(0)
const increment = () => {
count.value++
}
const reset = () => {
count.value = 0
}
// 暴露成员给外部 ref
defineExpose({ count, increment, reset })
return <div>计数:{count}</div>
}
// 父组件
function App() {
const counterRef = useRef()
onMounted(() => {
// 通过 ref 调用子组件暴露的方法
counterRef.value?.increment()
})
return (
<div>
<Counter ref={counterRef} />
<button
onClick={() => {
counterRef.value?.increment()
}}
>
外部增加
</button>
<button
onClick={() => {
counterRef.value?.reset()
}}
>
外部重置
</button>
</div>
)
}defineExpose() — 暴露组件内部成员
默认情况下,通过 ref 获取到的组件实例是一个空对象。你需要使用 defineExpose 显式地暴露内部成员,外部才能通过 ref 访问。
typescript
declare function defineExpose<T extends Record<string, any>>(exposed: T): voidexposed:要暴露的键值对对象,键为成员名称,值为对应的值或方法
tsx
import { defineExpose, ref } from 'vitarx'
function Editor() {
const content = ref('')
const getContent = () => {
return content.value
}
const setContent = (text: string) => {
content.value = text
}
const clear = () => {
content.value = ''
}
// 暴露方法和响应式数据
defineExpose({
content,
getContent,
setContent,
clear
})
return (
<textarea
value={content}
onInput={(e) => {
content.value = e.target.value
}}
/>
)
}
defineExpose必须在组件函数的顶层调用。
ref 绑定回调函数
除了传入 useRef() 创建的引用,ref 属性也支持传入一个回调函数。当元素或组件挂载时,回调函数会被调用并传入对应的实例:
tsx
import { onMounted } from 'vitarx'
function App() {
let divEl: HTMLDivElement | null = null
return (
<div
ref={(el) => {
// el 就是 DOM 元素
divEl = el
console.log('元素已挂载:', el)
}}
>
Hello
</div>
)
}完整示例
下面是一个综合运用 useRef 和 defineExpose 的完整示例——一个可外部控制的视频播放器组件:
tsx
import { ref, useRef, onMounted, onDispose, defineExpose } from 'vitarx'
// 视频播放器组件
function VideoPlayer(props: { src: string }) {
const videoRef = useRef<HTMLVideoElement>()
const isPlaying = ref(false)
const currentTime = ref(0)
const duration = ref(0)
onMounted(() => {
const video = videoRef.value
if (!video) return
video.addEventListener('play', () => {
isPlaying.value = true
})
video.addEventListener('pause', () => {
isPlaying.value = false
})
video.addEventListener('timeupdate', () => {
currentTime.value = video.currentTime
})
video.addEventListener('loadedmetadata', () => {
duration.value = video.duration
})
})
onDispose(() => {
// 组件销毁时暂停播放
videoRef.value?.pause()
})
// 暴露控制方法给外部
defineExpose({
isPlaying,
currentTime,
duration,
play: () => {
videoRef.value?.play()
},
pause: () => {
videoRef.value?.pause()
},
seek: (time: number) => {
if (videoRef.value) {
videoRef.value.currentTime = time
}
}
})
return (
<div class="video-player">
<video ref={videoRef} src={props.src} />
<div class="controls">
<button
onClick={() => {
videoRef.value?.play()
}}
>
播放
</button>
<button
onClick={() => {
videoRef.value?.pause()
}}
>
暂停
</button>
<span>
{currentTime} / {duration}
</span>
</div>
</div>
)
}
// 父组件 — 通过 ref 控制播放器
function App() {
const playerRef = useRef()
const handlePlay = () => {
playerRef.value?.play()
}
const handlePause = () => {
playerRef.value?.pause()
}
const handleSeek = () => {
// 跳转到第 30 秒
playerRef.value?.seek(30)
}
return (
<div>
<VideoPlayer ref={playerRef} src="/video.mp4" />
<div class="external-controls">
<button onClick={handlePlay}>外部播放</button>
<button onClick={handlePause}>外部暂停</button>
<button onClick={handleSeek}>跳转到 30 秒</button>
</div>
</div>
)
}