组件上下文 API
Vitarx 提供了一组上下文 API,让你在组件内部获取当前的应用实例、组件实例、视图对象和唯一 ID 等信息。这些 API 都必须在组件函数的顶层调用。
API 一览
| API | 返回值 | 说明 |
|---|---|---|
useApp() | App | 获取当前 App 实例 |
useInstance() | ComponentInstance | 获取当前组件实例 |
useView() | ComponentView | 获取当前组件视图 |
useId() | string | 生成应用内唯一 ID |
useApp() — 获取 App 实例
useApp 返回当前组件所属的 App 实例。如果组件不在任何 App 上下文中,会抛出错误。
declare function useApp<T extends App = App>(): T典型用途:访问应用级配置、调用应用级方法。
import { useApp } from 'vitarx'
function MyComponent() {
const app = useApp()
const handleReset = () => {
// 访问应用配置
console.log('ID 前缀:', app.config.idPrefix)
}
return <button onClick={handleReset}>查看配置</button>
}useInstance() — 获取组件实例
useInstance 返回当前组件的运行时实例对象。组件实例包含了组件的内部状态,如父组件引用、副作用作用域等。
declare function useInstance(): ComponentInstance组件实例主要面向框架内部使用,日常开发中较少直接操作。通常你会通过
defineExpose暴露需要的成员,而不是直接操作实例。
import { useInstance } from 'vitarx'
function MyComponent() {
const instance = useInstance()
// 检查组件是否已挂载
const checkMounted = () => {
console.log('是否已挂载:', instance.isMounted)
}
return <button onClick={checkMounted}>检查挂载状态</button>
}useView() — 获取组件视图
useView 返回当前组件的视图对象,它是组件在视图树中的节点表示。
declare function useView(): ComponentView与
useInstance类似,useView主要面向框架内部使用。useChildren内部就是通过useView来获取props.children的。
import { useView } from 'vitarx'
function MyComponent() {
const view = useView()
// 访问组件的 props
const logProps = () => {
console.log('组件 props:', view.props)
}
return <button onClick={logProps}>打印 Props</button>
}useId() — 生成唯一 ID
useId 生成一个应用内唯一的 ID 字符串,格式为 ${前缀}-${递增计数器}(如 v-0、v-1)。在组件内使用时,计数器基于 App 实例独立计数;在非组件环境中,使用全局计数器。
declare function useId(prefix?: string): stringprefix:可选的 ID 前缀,默认使用app.config.idPrefix(默认为v)
典型用途:为表单元素生成唯一的 id 属性,配合 label 使用。
import { useId } from 'vitarx'
function FormField(props: { label: string; type?: string }) {
// 生成唯一 ID,如 'field-0'
const id = useId('field')
return (
<div class="form-field">
<label for={id}>{props.label}</label>
<input id={id} type={props.type || 'text'} />
</div>
)
}
function LoginForm() {
return (
<form>
<FormField label="用户名" type="text" />
<FormField label="密码" type="password" />
</form>
)
}推荐在组件内使用
useId,这样 ID 计数器会跟随 App 实例,避免不同应用实例之间的 ID 冲突。
getInstance / getComponentView
除了 useXxx 形式的 API,Vitarx 还提供了 getInstance 和 getComponentView 函数,它们是 useInstance 和 useView 的底层版本,支持传入 allowEmpty 参数:
// 不允许返回空值,无实例时抛错
declare function getInstance(): ComponentInstance
declare function getComponentView(): ComponentView
// 允许返回空值
declare function getInstance(allowEmpty: true): ComponentInstance | null
declare function getComponentView(allowEmpty: true): ComponentView | null通常你只需要使用 useInstance 和 useView,它们在无实例时会抛出明确的错误信息。
完整示例
下面是一个综合运用上下文 API 的完整示例——一个可访问性友好的表单组件:
import { ref, useApp, useInstance, useId, onMounted, onDispose } from 'vitarx'
interface TextFieldProps {
label: string
type?: string
value?: string
error?: string
'onUpdate:value'?: (val: string) => void
}
function TextField(props: TextFieldProps) {
// 生成唯一 ID,用于 label 和 input 的关联
const inputId = useId('input')
const errorId = useId('error')
// 获取组件实例,用于检查挂载状态
const instance = useInstance()
// 获取 App 实例
const app = useApp()
const isFocused = ref(false)
let resizeObserver: ResizeObserver | null = null
onMounted(() => {
console.log(`[TextField] 已挂载,isMounted = #123;instance.isMounted}`)
console.log(`[TextField] 应用 ID 前缀 = #123;app.config.idPrefix}`)
})
onDispose(() => {
resizeObserver?.disconnect()
})
return (
<div class={`text-field #123;isFocused.value ? 'focused' : ''} #123;props.error ? 'has-error' : ''}`}>
<label for={inputId}>{props.label}</label>
<input
id={inputId}
type={props.type || 'text'}
value={props.value}
onInput={(e) => {
props['onUpdate:value']?.(e.target.value)
}}
onFocus={() => {
isFocused.value = true
}}
onBlur={() => {
isFocused.value = false
}}
aria-invalid={!!props.error}
aria-describedby={props.error ? errorId : undefined}
/>
{props.error && (
<p id={errorId} class="error-message" role="alert">
{props.error}
</p>
)}
</div>
)
}
// 使用
function ContactForm() {
const name = ref('')
const email = ref('')
const nameError = ref('')
const emailError = ref('')
const validate = () => {
nameError.value = name.value.trim() ? '' : '请输入姓名'
emailError.value = email.value.includes('@') ? '' : '请输入有效的邮箱地址'
}
return (
<div class="contact-form">
<h2>联系我们</h2>
<TextField
label="姓名"
value={name}
error={nameError}
onUpdate:value={(v) => {
name.value = v
}}
/>
<TextField
label="邮箱"
type="email"
value={email}
error={emailError}
onUpdate:value={(v) => {
email.value = v
}}
/>
<button onClick={validate}>提交</button>
</div>
)
}