Skip to content

参考

基于 Vue 3.5.x 编写 —— 全局 API、响应式 API、生命周期、编译宏、内置组件 / 指令、TypeScript 工具类型完整速查

全局 API

应用创建

API签名用途
createApp(rootComponent, rootProps?)Component => App创建客户端应用实例
createSSRApp(rootComponent, rootProps?)Component => App创建 SSR 应用(客户端 hydrate)
defineComponent(options)Options => Component类型化定义组件(不用 <script setup> 时)
defineAsyncComponent(loader | options)... => AsyncComponent异步组件
defineCustomElement(component)... => CustomElement编译成原生 Web Component
ts
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App, { initialCount: 5 })
app.mount('#app')

General

API用途
versionVue 版本号字符串(如 3.5.34
nextTick(cb?)下一次 DOM 更新后执行(返回 Promise)
defineComponent()TS 包装,给 Options API 加类型
h(type, props?, children?)创建 vnode(渲染函数用)
mergeProps(...args)合并多个 props 对象
useCssModule(name?)在 setup 内拿到 <style module> 的对象
useCssVars(getter)注入 <style v-bind> 等价的 CSS 变量
withDirectives(vnode, dirs)给 vnode 加指令(渲染函数用)
withModifiers(fn, modifiers)给事件处理函数加修饰符(渲染函数用)
withKeys(fn, keys)给事件处理函数加按键修饰符
ts
import { nextTick } from 'vue'

count.value++
await nextTick()
// 现在 DOM 已更新,可以安全读 DOM

应用实例 API

ts
const app = createApp(App)

app
  .component('MyButton', MyButton)         // 全局组件
  .directive('focus', vFocus)              // 全局指令
  .use(router)                              // 装插件
  .use(createPinia())
  .provide('apiBase', '/api')              // 全局 provide
  .mount('#app')                            // 挂载

// 卸载
app.unmount()

完整 App 实例方法

方法用途
app.mount(selector | element)挂载到 DOM
app.unmount()卸载
app.component(name, component?)注册 / 查询全局组件
app.directive(name, directive?)注册 / 查询全局指令
app.use(plugin, options?)装插件
app.mixin(mixin)全局 mixin(不推荐)
app.provide(key, value)全局 provide
app.runWithContext(fn)在 app context 下跑函数(拿到 inject)

app.config

属性类型用途
app.config.errorHandler(err, instance, info) => void全局错误处理
app.config.warnHandler(msg, instance, trace) => void全局警告处理(dev)
app.config.performanceboolean启用性能跟踪(DevTools)
app.config.compilerOptionsobject模板编译器选项(如自定义元素)
app.config.globalPropertiesobject添加全局属性(Options API 用 this.$xxx
app.config.optionMergeStrategiesobject自定义 options 合并策略
ts
app.config.errorHandler = (err, instance, info) => {
  console.error('[global error]', err, info)
  reportError(err)
}

app.config.globalProperties.$apiBase = '/api'

响应式 API

Core

API签名用途
ref(value)<T>(value: T) => Ref<T>单值响应式
reactive(target)<T extends object>(target: T) => T对象深响应式
computed(getter | options)<T>(...) => ComputedRef<T>派生值(带缓存)
readonly(target)<T>(target: T) => DeepReadonly<T>只读包装
watch(source, cb, opts?)(source, cb, opts) => stopHandle显式 watch
watchEffect(fn, opts?)(fn, opts) => stopHandle自动追踪 watch
watchPostEffect(fn)(fn) => stopHandleflush: 'post' 的 watchEffect
watchSyncEffect(fn)(fn) => stopHandleflush: 'sync' 的 watchEffect

Utilities

API用途
isRef(value)判断是否 ref
unref(value)拆 ref(非 ref 原值返回)
toRef(reactive, key) / toRef(getter) / toRef(value)转单字段为 ref(reactive 解构)
toValue(refOrGetter)3.3+ 把 ref / getter / 值都标准化成普通值
toRefs(reactive)把 reactive 对象的所有属性转 ref
isProxy(value)是否 reactive / readonly / shallowReactive / shallowReadonly 创建的代理
isReactive(value)是否 reactive / shallowReactive 创建
isReadonly(value)是否 readonly 包装

Advanced

API用途
shallowRef(value)浅响应 ref(只追踪 .value 替换)
triggerRef(ref)手动触发 shallowRef 的更新
customRef(factory)自定义 track / trigger 的 ref
shallowReactive(target)只有根级 reactive
shallowReadonly(target)只有根级 readonly
toRaw(reactive)取出原始非代理对象
markRaw(target)标记永不被代理
effectScope(detached?)创建 effect 作用域(批量管理)
getCurrentScope()当前 effect scope(如有)
onScopeDispose(fn)scope 停止时回调
ts
import {
  ref, reactive, computed, watch, watchEffect,
  toRef, toRefs, toValue, unref,
  shallowRef, triggerRef, customRef,
  markRaw, toRaw, readonly,
  effectScope, onScopeDispose,
} from 'vue'

// 例:完整组合
const state = reactive({ count: 0 })
const { count } = toRefs(state)
const doubled = computed(() => count.value * 2)
watch(count, (val) => console.log(val))

onWatcherCleanup (3.5+)

ts
import { watch, onWatcherCleanup } from 'vue'

watch(userId, async (id) => {
  const controller = new AbortController()
  onWatcherCleanup(() => controller.abort())

  const data = await fetch(`/api/users/${id}`, { signal: controller.signal })
})

生命周期钩子

Composition API

ts
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered,
  onActivated,
  onDeactivated,
  onServerPrefetch,
} from 'vue'
Hook触发时机
onBeforeMount挂载前(DOM 未生成)
onMounted挂载后(DOM 可访问)
onBeforeUpdate响应式更新前
onUpdated响应式更新后
onBeforeUnmount卸载前
onUnmounted卸载后
onErrorCaptured(err, instance, info)捕获子孙错误(return false 阻止冒泡)
onRenderTracked(event)收集依赖时(仅 dev)
onRenderTriggered(event)触发更新时(仅 dev)
onActivatedKeepAlive 缓存激活
onDeactivatedKeepAlive 缓存停用
onServerPrefetch(asyncFn)SSR 异步预取(仅 SSR)

Options API 等价

Options 写法Composition
beforeCreatesetup() 本身)
createdsetup() 本身)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroy (V2) / beforeUnmount (V3)onBeforeUnmount
destroyed (V2) / unmounted (V3)onUnmounted
errorCapturedonErrorCaptured
activatedonActivated
deactivatedonDeactivated

依赖注入

API签名用途
provide(key, value)(key, value) => void提供数据给后代
inject(key, default?, treatDefaultAsFactory?)(key, def, fac?) => T注入祖先提供的数据
hasInjectionContext()() => boolean3.3+ 是否在 inject 可用的上下文
ts
import { provide, inject, type InjectionKey, type Ref } from 'vue'

// 类型化 key
const ThemeKey: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme')

// 提供
provide(ThemeKey, theme)

// 注入(自动类型推导)
const theme = inject(ThemeKey)
const fallback = inject(ThemeKey, ref('light'))
const lazyFallback = inject(ThemeKey, () => createTheme(), true)

编译器宏(<script setup> 内)

用途
defineProps<T>() / defineProps(opts)声明 props
defineEmits<T>() / defineEmits(opts)声明 emits
defineExpose({ ... })暴露给父组件 ref
defineModel<T>(name?, opts?)3.4+ 双向绑定
defineOptions({ ... })等价 Options 顶层(name / inheritAttrs 等)
defineSlots<T>()类型化 slots
withDefaults(defineProps<T>(), defaults)给 defineProps 加默认值
useSlots() / useAttrs()取 $slots / $attrs
useId()3.5+ 生成 SSR-safe 唯一 id
useTemplateRef(name)3.5+ 模板 ref 拿到响应式 ref
vue
<script setup lang="ts">
// 完整示例
interface Props { title: string; count?: number }
const { title, count = 0 } = defineProps<Props>()    // 3.5+ 响应式解构

const emit = defineEmits<{
  submit: [data: { id: number }]
  cancel: []
}>()

defineExpose({ focus })
defineOptions({ name: 'MyForm', inheritAttrs: false })

const model = defineModel<string>({ required: true })

const inputRef = useTemplateRef<HTMLInputElement>('input')
const id = useId()
</script>

<template>
  <input ref="input" :id="id" v-model="model" />
</template>

内置组件

组件主要 props用途
<Transition>name / mode / appear / css / duration / type进出动画
<TransitionGroup>同上 + tag / moveClass列表动画(含 FLIP move)
<KeepAlive>include / exclude / max缓存组件
<Teleport>to (必填) / disabled / defer (3.5+)传送 DOM 到指定容器
<Suspense>timeout / suspensible等待异步子组件

<Transition> 全部 props

Prop类型默认
namestring'v'
appearbooleanfalse
persistedbooleanfalse
cssbooleantrue
type'transition' | 'animation'自动检测
durationnumber / { enter, leave }自动
mode'out-in' | 'in-out' | undefinedundefined
enterFromClassstring${name}-enter-from
enterActiveClassstring${name}-enter-active
enterToClassstring${name}-enter-to
leaveFromClassstring${name}-leave-from
leaveActiveClassstring${name}-leave-active
leaveToClassstring${name}-leave-to
appearFromClassstring(同 enter)
appearActiveClassstring(同 enter)
appearToClassstring(同 enter)

<Transition> events

@before-enter / @enter(el, done) / @after-enter / @enter-cancelled / @before-leave / @leave(el, done) / @after-leave / @leave-cancelled / @appear 等。

内置特殊属性

属性用途
key强制识别同 / 异元素(v-for / 组件切换关键)
ref模板引用(指向 DOM 或组件实例)
is动态组件 <component :is="...">
v-pre跳过模板编译(输出原始 {{ }}
v-cloak隐藏未编译模板(避免闪烁)
v-once只渲染一次,不再更新
v-memo="[deps]"依赖未变跳过子树 diff
vue
<template>
  <!-- 动态组件 -->
  <component :is="currentView" :data="data" />

  <!-- v-pre:输出原始模板 -->
  <span v-pre>{{ this will not be compiled }}</span>

  <!-- v-cloak + CSS 隐藏未编译模板 -->
  <div v-cloak>{{ message }}</div>
</template>

<style>
[v-cloak] { display: none; }
</style>

内置指令完整表

指令用途简写
v-text设 textContent-
v-html设 innerHTML(XSS 风险)-
v-show切换 CSS display-
v-if / v-else-if / v-else条件渲染(真销毁)-
v-for="item in list"列表渲染-
v-on:event="handler"事件绑定@event
v-bind:attr="value"属性绑定:attr
v-model双向绑定(input / 组件)-
v-slot:name="props"具名 / 作用域插槽#name
v-pre跳过编译-
v-once一次性渲染-
v-memo依赖数组缓存子树-
v-cloak未编译时隐藏-

事件修饰符

修饰符等价
.stopevent.stopPropagation()
.preventevent.preventDefault()
.capture捕获阶段监听
.self仅当 event.target === el
.once只触发一次
.passiveaddEventListener({ passive: true })

按键修饰符

.enter / .tab / .delete / .esc / .space / .up / .down / .left / .right / .ctrl / .alt / .shift / .meta / .exact

鼠标按键修饰符

.left / .right / .middle

v-model 修饰符

修饰符用途
.lazy使用 change 事件而非 input
.number自动转 number
.trim去首尾空白

TypeScript 工具类型

类型用途
Ref<T>ref 类型
ComputedRef<T>computed 返回类型
WritableComputedRef<T>可写 computed
ShallowRef<T>shallowRef 类型
Reactive<T>reactive 包装的对象类型
MaybeRef<T>T | Ref<T>
MaybeRefOrGetter<T>T | Ref<T> | (() => T)
UnwrapRef<T>拆 ref 层
PropType<T>Options API props 类型断言
ComponentPropsOptions整个 props 配置类型
ExtractPropTypes<T>从 props 配置反推 props 类型
ExtractPublicPropTypes<T>同上但只含 public(不含 default)
DefineComponent<...>defineComponent 返回类型
Component / ComponentInstance通用组件 / 实例类型
Slot<T> / Slotsslot 函数
InjectionKey<T>类型化 inject key
Directive<Element, Value, Modifiers>自定义指令类型
DirectiveBinding<Value>指令 binding 参数
App<HostElement>应用实例
ts
import type {
  Ref, ComputedRef, MaybeRef, MaybeRefOrGetter,
  PropType, ExtractPropTypes, DefineComponent,
  Directive, DirectiveBinding,
  InjectionKey, Slots,
} from 'vue'

// MaybeRef 让函数接受值或 ref
function useDouble(input: MaybeRef<number>) {
  return computed(() => unref(input) * 2)
}

// 类型化 InjectionKey
const ApiKey: InjectionKey<ApiClient> = Symbol('api')

配置项(app.config

ts
const app = createApp(App)

// 错误处理
app.config.errorHandler = (err, instance, info) => { /* ... */ }
app.config.warnHandler = (msg, instance, trace) => { /* ... */ }

// 性能跟踪(DevTools 可见)
app.config.performance = true

// 全局属性
app.config.globalProperties.$apiBase = '/api'
app.config.globalProperties.$formatDate = (d: Date) => d.toISOString()

// 编译器选项
app.config.compilerOptions = {
  isCustomElement: (tag) => tag.startsWith('my-'),  // 跳过这些标签的组件解析
  whitespace: 'condense',                            // 'preserve' | 'condense'
  delimiters: ['${', '}'],                            // 改插值定界符(默认 {{ }})
  comments: false,                                    // 是否保留注释
}

// 选项合并策略(极少用)
app.config.optionMergeStrategies.myOption = (parent, child) => ({ ...parent, ...child })

Vue Router 4 主要 API

创建

ts
import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/users/:id', name: 'user', component: User, meta: { requiresAuth: true } },
    { path: '/:pathMatch(.*)*', component: NotFound },
  ],
  scrollBehavior(to, from, savedPosition) { /* ... */ },
})

守卫

守卫触发
router.beforeEach((to, from) => ...)全局前置
router.beforeResolve((to) => ...)解析后(组件守卫之后)
router.afterEach((to, from, failure?) => ...)全局后置(无返回值)
route.beforeEnter路由独享守卫
onBeforeRouteLeave((to, from) => ...)组件内:离开
onBeforeRouteUpdate((to, from) => ...)组件内:同组件参数变化

Composables

API用途
useRoute()当前路由对象(响应式 readonly)
useRouter()router 实例(push / replace / back / forward)
useLink(props)编程获取 RouterLink 等同行为
onBeforeRouteUpdate(fn)组件内更新守卫
onBeforeRouteLeave(fn)组件内离开守卫

Router 实例方法

ts
const router = useRouter()

router.push('/about')
router.push({ name: 'user', params: { id: 42 } })
router.push({ path: '/search', query: { q: 'vue' } })

router.replace('/login')   // 不进历史
router.go(-1)               // 等价 history.go(-1)
router.back()
router.forward()

router.addRoute({ path: '/new', component: NewView })
router.removeRoute('user')
router.hasRoute('user')
router.getRoutes()

Pinia 主要 API

创建 Store

ts
import { defineStore } from 'pinia'

// Setup Store(推荐)
export const useStore = defineStore('id', () => {
  const state = ref(...)
  const getter = computed(...)
  function action() { ... }
  return { state, getter, action }
})

// Option Store
export const useStore = defineStore('id', {
  state: () => ({ ... }),
  getters: { doubled: (state) => state.count * 2 },
  actions: { increment() { this.count++ } },
})

Store 实例方法

方法用途
store.$idstore id
store.$state直接访问 / 替换 state
store.$patch(partialState)批量修改
store.$patch((state) => ...)函数式批量修改
store.$reset()重置到 state() 初始值(Option Store 自动;Setup Store 要自己实现)
store.$subscribe((mutation, state) => ...)监听 state 变化
store.$onAction(({ name, args, after, onError }) => ...)监听 action
store.$dispose()卸载 store

storeToRefs —— 解构保留响应

ts
import { storeToRefs } from 'pinia'

const counter = useCounterStore()
const { count, doubled } = storeToRefs(counter)   // state + getter 保持响应
const { increment } = counter                       // actions 直接拿

版本里程碑

版本时间关键特性
0.x2013~2014初版
1.0 "Evangelion"2015.10首个稳定版
2.0 "Ghost in the Shell"2016.9Virtual DOM、Server Rendering
2.12016.11<transition-group>
2.52017.10TypeScript 大幅改进
2.62019.2新插槽语法(v-slot
2.7 "Naruto"2022.7移植 Composition API + <script setup> 到 Vue 2
3.0 "One Piece"2020.9重写:Composition API、Proxy 响应式、Fragments、Teleport、Suspense
3.12021.6Migration Build(兼容 v2)
3.2 "Quintessential Quintuplets"2021.8<script setup> 稳定、Web Components、SFC 多 <style>
3.3 "Rurouni Kenshin"2023.5defineEmits 短签名、defineSlots、Generic Components、toValue
3.4 "Slam Dunk"2023.12defineModel 稳定、性能提升、v-bind 同名简写
3.5 "Tengen Toppa Gurren Lagann"2024.9反应式 props 解构默认稳定、useId / useTemplateRef / onWatcherCleanup、Lazy Hydration、Teleport defer
3.x.x持续迭代bug 修复 + 小特性
Vapor Mode孵化中跳过 VDOM 编译输出(vapor-alpha-branch

完整片段拷贝即用

完整 main.ts

ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'

const app = createApp(App)

// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
  console.error('[global]', err, info)
}

// 全局属性
app.config.globalProperties.$apiBase = import.meta.env.VITE_API_BASE

app
  .use(createPinia())
  .use(router)
  .mount('#app')

完整 router/index.ts

ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/HomeView.vue'),
  },
  {
    path: '/users/:id(\\d+)',
    name: 'user',
    component: () => import('@/views/UserView.vue'),
    props: true,
    meta: { requiresAuth: true },
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('@/views/NotFoundView.vue'),
  },
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition
    if (to.hash) return { el: to.hash, behavior: 'smooth' }
    return { top: 0 }
  },
})

router.beforeEach((to) => {
  const auth = useAuthStore()
  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})

export default router

完整 Pinia store

ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null)
  const token = ref<string | null>(localStorage.getItem('token'))

  const isLoggedIn = computed(() => user.value !== null)
  const userName = computed(() => user.value?.name ?? 'Guest')

  async function login(email: string, password: string) {
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
      headers: { 'Content-Type': 'application/json' },
    })
    if (!res.ok) throw new Error('Login failed')

    const data = await res.json()
    user.value = data.user
    token.value = data.token
    localStorage.setItem('token', data.token)
  }

  function logout() {
    user.value = null
    token.value = null
    localStorage.removeItem('token')
  }

  return { user, token, isLoggedIn, userName, login, logout }
})

通用 SFC 模板

vue
<script setup lang="ts">
import { ref, computed, watch, onMounted, useTemplateRef } from 'vue'

// Props + Emits
interface Props {
  initialValue?: number
}
const { initialValue = 0 } = defineProps<Props>()

const emit = defineEmits<{
  change: [value: number]
}>()

// 状态
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)

// 模板引用
const buttonRef = useTemplateRef<HTMLButtonElement>('btn')

// 副作用
watch(count, (val) => emit('change', val))

onMounted(() => {
  buttonRef.value?.focus()
})

// 事件
function increment() {
  count.value++
}

// 暴露
defineExpose({ count, increment })
</script>

<template>
  <div class="counter">
    <span>{{ count }} ({{ doubled }})</span>
    <button ref="btn" @click="increment">+1</button>
  </div>
</template>

<style scoped>
.counter {
  display: flex;
  gap: 8px;
  align-items: center;
}
button {
  padding: 4px 12px;
}
</style>

完整自定义指令

ts
// directives/clickOutside.ts
import type { Directive } from 'vue'

type Handler = (e: MouseEvent) => void

interface ClickOutsideElement extends HTMLElement {
  _clickOutsideHandler?: (e: MouseEvent) => void
}

export const vClickOutside: Directive<ClickOutsideElement, Handler> = {
  mounted(el, binding) {
    el._clickOutsideHandler = (e) => {
      if (!el.contains(e.target as Node)) {
        binding.value(e)
      }
    }
    document.addEventListener('click', el._clickOutsideHandler)
  },
  unmounted(el) {
    if (el._clickOutsideHandler) {
      document.removeEventListener('click', el._clickOutsideHandler)
      delete el._clickOutsideHandler
    }
  },
}
ts
// main.ts 注册
import { vClickOutside } from './directives/clickOutside'
app.directive('click-outside', vClickOutside)
vue
<template>
  <div v-click-outside="closeDropdown">Dropdown content</div>
</template>

完整 Composable

ts
// composables/useFetchData.ts
import { ref, watch, type Ref } from 'vue'
import { onWatcherCleanup } from 'vue'

export interface UseFetchOptions {
  immediate?: boolean
}

export function useFetchData<T>(
  url: Ref<string> | (() => string),
  options: UseFetchOptions = {},
) {
  const data = ref<T | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  async function execute(currentUrl: string) {
    const controller = new AbortController()
    onWatcherCleanup(() => controller.abort())

    loading.value = true
    error.value = null

    try {
      const res = await fetch(currentUrl, { signal: controller.signal })
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      data.value = await res.json()
    } catch (e) {
      if ((e as Error).name !== 'AbortError') {
        error.value = e as Error
      }
    } finally {
      loading.value = false
    }
  }

  watch(
    typeof url === 'function' ? url : url,
    execute,
    { immediate: options.immediate ?? true },
  )

  return { data, loading, error }
}

完整 plugin

ts
// plugins/apiClient.ts
import type { App } from 'vue'

export interface ApiClientOptions {
  baseURL?: string
  headers?: Record<string, string>
}

export class ApiClient {
  constructor(private options: ApiClientOptions = {}) {}

  async get<T>(url: string): Promise<T> {
    const res = await fetch(`${this.options.baseURL ?? ''}${url}`, {
      headers: this.options.headers,
    })
    return res.json()
  }

  async post<T>(url: string, body: any): Promise<T> {
    const res = await fetch(`${this.options.baseURL ?? ''}${url}`, {
      method: 'POST',
      headers: { ...this.options.headers, 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })
    return res.json()
  }
}

export const ApiClientKey = Symbol('apiClient')

export default {
  install(app: App, options: ApiClientOptions = {}) {
    const client = new ApiClient(options)
    app.provide(ApiClientKey, client)
    app.config.globalProperties.$api = client
  },
}
ts
// 类型扩展
declare module 'vue' {
  interface ComponentCustomProperties {
    $api: ApiClient
  }
}
ts
// main.ts
import ApiClientPlugin from './plugins/apiClient'

app.use(ApiClientPlugin, {
  baseURL: import.meta.env.VITE_API_BASE,
})
vue
<script setup lang="ts">
import { inject } from 'vue'
import { ApiClientKey, type ApiClient } from '@/plugins/apiClient'

const api = inject(ApiClientKey) as ApiClient
const data = await api.get('/users')
</script>

速查链接