组件上下文 API

Vitarx 提供了一组上下文 API,让你在组件内部获取当前的应用实例、组件实例、视图对象和唯一 ID 等信息。这些 API 都必须在组件函数的顶层调用。

API 一览

API返回值说明
useApp()App获取当前 App 实例
useInstance()ComponentInstance获取当前组件实例
useView()ComponentView获取当前组件视图
useId()string生成应用内唯一 ID

useApp() — 获取 App 实例

useApp 返回当前组件所属的 App 实例。如果组件不在任何 App 上下文中,会抛出错误。

typescript
declare function useApp<T extends App = App>(): T

典型用途:访问应用级配置、调用应用级方法。

tsx
import { useApp } from 'vitarx'

function MyComponent() {
  const app = useApp()

  const handleReset = () => {
    // 访问应用配置
    console.log('ID 前缀:', app.config.idPrefix)
  }

  return <button onClick={handleReset}>查看配置</button>
}

useInstance() — 获取组件实例

useInstance 返回当前组件的运行时实例对象。组件实例包含了组件的内部状态,如父组件引用、副作用作用域等。

typescript
declare function useInstance(): ComponentInstance

组件实例主要面向框架内部使用,日常开发中较少直接操作。通常你会通过 defineExpose 暴露需要的成员,而不是直接操作实例。

tsx
import { useInstance } from 'vitarx'

function MyComponent() {
  const instance = useInstance()

  // 检查组件是否已挂载
  const checkMounted = () => {
    console.log('是否已挂载:', instance.isMounted)
  }

  return <button onClick={checkMounted}>检查挂载状态</button>
}

useView() — 获取组件视图

useView 返回当前组件的视图对象,它是组件在视图树中的节点表示。

typescript
declare function useView(): ComponentView

useInstance 类似,useView 主要面向框架内部使用。useChildren 内部就是通过 useView 来获取 props.children 的。

tsx
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-0v-1)。在组件内使用时,计数器基于 App 实例独立计数;在非组件环境中,使用全局计数器。

typescript
declare function useId(prefix?: string): string
  • prefix:可选的 ID 前缀,默认使用 app.config.idPrefix(默认为 v

典型用途:为表单元素生成唯一的 id 属性,配合 label 使用。

tsx
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 还提供了 getInstancegetComponentView 函数,它们是 useInstanceuseView 的底层版本,支持传入 allowEmpty 参数:

typescript
// 不允许返回空值,无实例时抛错
declare function getInstance(): ComponentInstance
declare function getComponentView(): ComponentView

// 允许返回空值
declare function getInstance(allowEmpty: true): ComponentInstance | null
declare function getComponentView(allowEmpty: true): ComponentView | null

通常你只需要使用 useInstanceuseView,它们在无实例时会抛出明确的错误信息。

完整示例

下面是一个综合运用上下文 API 的完整示例——一个可访问性友好的表单组件:

tsx
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>
  )
}

下一步