Vue3状态管理的两种主流模式
Vue3组合式API带来了更灵活的状态管理方式。在中小型项目中,开发者面临一个选择:使用Pinia做全局状态管理,还是用provide/inject实现组件树内的状态共享。两种方案各有适用场景,选错会增加不必要的复杂度或限制组件的复用性。
Pinia:全局状态管理方案
Pinia是Vue3官方推荐的状态管理库,支持组合式API风格的Store定义:
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const token = ref('')
const userInfo = ref(null)
const isLoggedIn = computed(() => !!token.value)
async function login(credentials) {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
const data = await res.json()
token.value = data.token
userInfo.value = data.user
}
function logout() {
token.value = ''
userInfo.value = null
}
return { token, userInfo, isLoggedIn, login, logout }
})
Pinia的优势在于状态的全局可达性——任何组件无需知道父组件是谁,直接调用useUserStore()即可访问和修改状态。这让它适合管理跨模块共享的状态:用户认证信息、全局配置、权限列表等。
provide/inject:组件树局部状态共享
provide/inject是Vue3内置的依赖注入机制,状态只在组件树的特定子树中可用:
// composables/useFormContext.ts
import { provide, inject, ref, readonly } from 'vue'
const FORM_KEY = Symbol('form')
export function provideFormContext(options) {
const formData = ref(options.initialValues)
const errors = ref({})
const setField = (key, value) => {
formData.value[key] = value
}
const validate = () => {
return Object.keys(errors.value).length === 0
}
const submit = async () => {
if (validate()) {
await options.onSubmit(formData.value)
}
}
provide(FORM_KEY, {
formData: readonly(formData),
errors: readonly(errors),
setField,
validate,
submit
})
}
export function useFormContext() {
const ctx = inject(FORM_KEY)
if (!ctx) throw new Error('useFormContext must be used within FormProvider')
return ctx
}
provide/inject的特点是状态的作用域天然受限于组件树。同一个页面可以渲染多个独立的Form实例,每个实例的状态互不干扰,因为它们各自处于不同的provide/inject作用域中。
两种方案的核心差异对比
1. 作用域:Pinia的Store是全局单例,所有组件共享同一份状态。provide/inject的状态作用域由组件树位置决定,同一组件可以渲染多份独立状态。
2. 生命周期:Pinia的Store在应用整个生命周期内存在(除非手动销毁)。provide/inject的状态随提供者组件的挂载/卸载而创建/销毁。
3. 调试能力:Pinia集成Vue DevTools,可追踪状态变更时间线。provide/inject的状态变更对DevTools不可见。
4. SSR支持:Pinia通过pinia plugin实现SSR状态序列化和hydration。provide/inject需要手动处理服务端状态传递。
5. 测试便利性:Pinia可通过createPinia()创建测试专用实例。provide/inject需要在测试中手动包裹provider组件。
实际项目中的选型决策
以下场景优先使用Pinia:
– 用户登录状态、权限信息等跨模块全局状态
– 需要持久化到localStorage的状态
– 需要在DevTools中调试状态变更的场景
– 多个不相关组件需要访问同一份数据
以下场景优先使用provide/inject:
– 表单、对话框等需要多实例独立运行的组件
– 组件库内部状态共享,不暴露给外部消费者
– UI组件与业务逻辑解耦,组件自身不关心Pinia
– 组件可能在不同页面重复使用,每次使用需要独立状态
混合使用时的边界划分
实际项目中两种方案可以共存,关键在于划定各自的职责边界。一条实用规则:如果一个状态的生命周期与应用生命周期一致,放Pinia;如果与某个UI组件的生命周期绑定,用provide/inject。
典型混合方案:用户认证信息(token、角色)放Pinia,表单编辑过程中的临时状态(草稿、校验错误)用provide/inject。表单提交后调用Pinia的action更新全局状态,表单组件卸载后草稿状态自动清理。
// 表单组件混合两种方案
export default {
setup() {
const userStore = useUserStore()
const formCtx = useFormContext()
onMounted(() => {
// 从Pinia读取初始值填充表单
formCtx.setField('username', userStore.userInfo.name)
})
const handleSubmit = async () => {
const values = formCtx.formData.value
// 提交后更新全局状态
await userStore.updateProfile(values)
}
return { handleSubmit }
}
}
这种分层方式让全局状态保持精简(只存最终确认的数据),临时状态在组件树中自包含,不会污染全局Store。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhong-pinia-yu-provideinject-zhuang-tai/