依赖注入
当组件层级较深时,通过 props 一层层传递数据会非常繁琐。Vitarx 提供了 provide / inject 依赖注入机制,让祖先组件可以直接向任意后代组件传递数据,无需中间组件逐层转发。
provide — 提供依赖
provide 在当前组件中注册一个依赖,后代组件可以通过 inject 获取。
typescript
declare function provide(name: string | symbol, value: unknown): voidname:依赖的唯一标识,推荐使用字符串或 Symbolvalue:要提供的数据值
tsx
import { provide, ref } from 'vitarx'
function Parent() {
// 提供静态值
provide('theme', 'dark')
// 提供响应式值
const count = ref(0)
provide('count', count)
return <Child />
}
provide必须在组件函数的顶层调用(组件初始化阶段),不能在异步回调中使用。
inject — 注入依赖
inject 在后代组件中获取祖先组件提供的依赖数据。
typescript
// 不带默认值,返回 T | undefined
declare function inject<T>(name: string | symbol): T | undefined
// 带默认值
declare function inject<T>(name: string | symbol, defaultValue: T): T
// 默认值作为工厂函数
declare function inject<T>(
name: string | symbol,
defaultValue: () => T,
treatDefaultAsFactory: true
): Ttsx
import { inject } from 'vitarx'
function Child() {
// 注入依赖,不提供默认值时返回值可能为 undefined
const theme = inject<string>('theme')
// 注入依赖,提供默认值
const size = inject<string>('size', 'medium')
return <div class={`theme-#123;theme ?? 'light'} size-#123;size}`}>子组件</div>
}
inject同样必须在组件函数的顶层调用,不能在异步回调中使用。
组件级注入
组件级注入是最常见的用法。祖先组件通过 provide 提供数据,后代组件通过 inject 获取数据。查找顺序是从当前组件向上逐层查找父组件,直到找到为止。
tsx
import { provide, inject, ref } from 'vitarx'
// 祖先组件
function GrandParent() {
const userInfo = ref({ name: '小明', age: 18 })
provide('userInfo', userInfo)
return <Parent />
}
// 中间组件 — 无需关心 userInfo 的传递
function Parent() {
return <Child />
}
// 后代组件 — 直接注入使用
function Child() {
const userInfo = inject<{ name: string; age: number }>('userInfo')
return (
<div>
<p>姓名:{userInfo?.name}</p>
<p>年龄:{userInfo?.age}</p>
</div>
)
}应用级注入
除了组件级注入,还可以在 App 实例上提供全局依赖。当组件级注入找不到数据时,会继续在应用级依赖中查找。
tsx
import { createApp, inject } from 'vitarx'
function App() {
// 组件中可以通过 inject 获取 app 级别的依赖
const apiBase = inject<string>('apiBase')
return <div>API 地址:{apiBase}</div>
}
// 在创建应用时提供全局依赖
const app = createApp(App)
app.provide('apiBase', 'https://api.example.com')
app.mount(document.getElementById('root')!)应用级注入的查找优先级低于组件级注入:组件级 > 应用级。
响应式注入
当 provide 提供的是响应式数据(如 ref、reactive)时,inject 获取到的也是同一个响应式引用,因此数据变化会自动同步。
tsx
import { provide, inject, ref } from 'vitarx'
// 祖先组件
function ThemeProvider() {
const theme = ref('light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('theme', theme)
provide('toggleTheme', toggleTheme)
return <Child />
}
// 后代组件
function ThemedButton() {
const theme = inject<import('vitarx').Ref<string>>('theme')
const toggleTheme = inject<() => void>('toggleTheme')
return (
<button class={`btn btn-#123;theme?.value ?? 'light'}`} onClick={toggleTheme}>
切换主题
</button>
)
}treatDefaultAsFactory 参数
当默认值是一个函数时,inject 会把它当作普通值而不是工厂函数。如果你希望默认值作为工厂函数执行,需要将第三个参数 treatDefaultAsFactory 设为 true。
tsx
import { inject, reactive } from 'vitarx'
function Child() {
// ❌ 问题:inject 会把函数当作默认值,而不是执行它
// const config = inject('config', () => ({ debug: false }))
// ✅ 正确:使用 treatDefaultAsFactory 让默认值作为工厂函数执行
const config = inject('config', () => ({ debug: false }), true)
return <div>调试模式:{config.debug ? '开启' : '关闭'}</div>
}使用 Symbol 作为注入键
在大型项目中,推荐使用 Symbol 作为注入键,避免命名冲突:
tsx
import { provide, inject, type Ref } from 'vitarx'
// 在共享模块中定义 Symbol 键
export const ThemeKey = Symbol('theme')
export const UserKey = Symbol('user')
// 提供方
function App() {
provide(ThemeKey, 'dark')
provide(UserKey, { name: '小明', age: 18 })
return <Child />
}
// 注入方
function Child() {
const theme = inject<string>(ThemeKey, 'light')
const user = inject<{ name: string; age: number }>(UserKey)
return (
<div>
{theme} - {user?.name}
</div>
)
}完整示例
下面是一个完整的主题切换示例,展示了依赖注入的核心用法:
tsx
import { provide, inject, ref, type Ref } from 'vitarx'
// 用 Symbol 作为注入键,避免命名冲突
const ThemeKey = Symbol('theme')
const ToggleThemeKey = Symbol('toggleTheme')
// 主题提供者 — 在顶层提供主题数据
function ThemeProvider() {
const theme = ref<'light' | 'dark'>('light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
// 提供响应式主题和切换函数
provide(ThemeKey, theme)
provide(ToggleThemeKey, toggleTheme)
return (
<div class={`app theme-#123;theme}`}>
<Header />
<Main />
<Footer />
</div>
)
}
// 头部组件 — 深层嵌套,但可以直接注入主题
function Header() {
const theme = inject<Ref<'light' | 'dark'>>(ThemeKey)
const toggleTheme = inject<() => void>(ToggleThemeKey)
return (
<header>
<h1>我的应用</h1>
<button onClick={toggleTheme}>当前主题:{theme?.value},点击切换</button>
</header>
)
}
// 主内容区
function Main() {
const theme = inject<Ref<'light' | 'dark'>>(ThemeKey)
return (
<main>
<p>当前使用 {theme?.value} 主题</p>
</main>
)
}
// 底部组件
function Footer() {
const toggleTheme = inject<() => void>(ToggleThemeKey)
return (
<footer>
<button onClick={toggleTheme}>切换主题</button>
</footer>
)
}