最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Pinia在你的项目中可能已沦为第二个localStorage
时间:2026-06-02 15:40:01 编辑:袖梨 来源:一聚教程网
前端开发中,你是否遇到过这样的困境:随着业务迭代,Pinia store逐渐变成了第二个localStorage?本文将揭示状态管理的三层架构,并介绍Page Scope这一创新解决方案。
一段你大概率写过的代码
// 某个后台业务页面的 Pinia store
export const useOrderListStore = defineStore('orderList', {
state: () => ({
keyword: '',
page: 1,
pageSize: 20,
selectedIds: [],
deleteDialogVisible: false,
deleteConfirmLoading: false,
detailDrawerVisible: false,
currentDetailId: null,
columnsConfig: [],
tempEditDraft: null,
pollTimer: null, // ← 这条尤其刺眼
lastFetchedAt: null,
activeTab: 'basic',
}),
})
开发时看似合理,三个月后却不敢删除任何字段,半年后页面重构但store仍保留,因为无法确认是否还有组件依赖这些状态。

这并非Pinia的缺陷,而是前端状态管理的结构性问题:复杂页面的状态缺乏明确归属。
前端状态的三层分布
┌─────────────────────────────────────────────────┐
│ 应用级状态 │
│ 用户信息 / 权限 / 主题 / 路由 │ ← Pinia
│ 生命周期:跟应用一起活,通常不销毁 │
├─────────────────────────────────────────────────┤
│ 页面级状态 │
│ 筛选条件 / 表格分页 / 弹窗 / 轮询 / 草稿 │ ← Page Scope
│ 生命周期:跟页面可见性走,离开/销毁时回收 │ 长期被忽视的中间层
├─────────────────────────────────────────────────┤
│ 组件级状态 │
│ 输入框 / UI 局部态 / 私有交互 │ ← ref / reactive
│ 生命周期:跟组件实例走 │
└─────────────────────────────────────────────────┘
页面级状态常被忽视:它规模过大不适合组件,又不具备全局性不应污染Pinia。于是这些状态往往无家可归,要么被塞进Pinia变成持久化存储,要么通过ref和provide/inject勉强传递,要么手动实现页面作用域。
Page Scope 是什么
核心概念:为复杂页面创建隔离的响应式容器。
┌────────────────────────┐
│ Page Component │
│ (Owner: setup 内) │
└───────────┬────────────┘
│
│ useOrderScope()
▼
┌───────────────────────────────────────────┐
│ PageScope (基于 Vue 3 effectScope) │
│ │
│ source state getters │
│ actions watch $loading │
│ $setInterval event bus $route 桥接 │
│ plugins (任意外部扩展) │
│ │
└─────────────────────┬─────────────────────┘
│
│ 页面离开 / 销毁
▼
effectScope.stop()
↓ 自动回收所有响应式副作用 ↓
(watch / computed / $setInterval / plugin watchers)
Page Scope随页面创建而初始化,运行期间管理所有副作用,页面销毁时通过effectScope.stop()自动清理所有响应式依赖。
真实代码长什么样
1. 定义一个 Page Scope
// scopes/order-list.ts
import { definePageScope } from 'vue-page-scope'export const useOrderScope = definePageScope('orderList', {
// 页面输入 / 接口原始返回
source: () => ({
response: null,
query: {},
}), // 业务状态
state: () => ({
keyword: '',
page: 1,
selectedIds: [],
deleteDialogVisible: false,
}), // 派生计算
getters: {
list() { return this.$source.response?.list || [] },
total() { return this.$source.response?.total || 0 },
hasSelection() { return this.selectedIds.length > 0 },
}, // 业务方法 —— 返回 Promise 的 action 自动追踪 $loading
actions: {
async search() {
const res = await api.getOrders({
keyword: this.keyword,
page: this.page,
})
this.$source.response = res
},
}, // 一次性初始化(拉字典、注册等)
init() {
this.loadDictOptions()
}, // 每次页面可见时执行(keep-alive 切回也会)
enter() {
this.$source.query = this.$route.query // ← 直接用 $route,见下文 auto bridge
this.search()
this.$setInterval(() => this.search(), 5000) // ← 页面级定时器
}, // 离开时 $setInterval 自动清,通常不需要写
leave() {},
})
2. 页面组件里使用
<script setup>
import { useOrderScope } from '../scopes/order-list'// 必须在 setup 内调用,该组件成为 scope 的 owner
// 不需要传 $route / $router —— 框架自动桥接
const orderScope = useOrderScope()
script><template>
<input v-model="orderScope.keyword" />
<button
:loading="orderScope.$loading.search"
@click="orderScope.search"
>
搜索
button>
<p>共 {{ orderScope.total }} 条p>
template>
3. 子组件不需要 import
<script setup>
import { injectPageScope } from 'vue-page-scope'const scope = injectPageScope() // ← 自动拿到父级页面的 scope
script><template>
<input v-model="scope.keyword" />
template>
子组件通过injectPageScope()自动获取父级页面scope,无需耦合具体scope实现。
几个能直接看出价值的细节
Auto Bridge:router 不用手动传
传统方案需要手动注入路由实例,而Page Scope通过auto bridge自动集成路由系统:
const orderScope = useOrderScope({
$route: microAppRoute, // 显式注入优先级高于 auto bridge
$router: microAppRouter,
$user: useUserStore(), // 也可以注入任意 composables
})
$loading 自动追踪
异步action自动追踪loading状态,无需手动维护:
"scope.$loading.search" @click="scope.search">
搜索
一次销毁,全部回收
页面销毁时自动清理所有副作用:
effectScope.stop()
↓
所有 watch / computed 释放
$setInterval 清理
plugin destroy 钩子触发
事件总线清空
为什么用 effectScope
Vue 3.2引入的effectScope专为管理响应式作用域设计:
const scope = effectScope(true) // detached,不被父 scope 收编scope.run(() => {
const state = reactive({ keyword: '' })
watch(() => state.keyword, () => { /* ... */ })
})scope.stop() // 所有 watch / computed 一行释放
Page Scope将这一能力提升到页面维度,每个scope都是独立的effectScope实例。
它和 Pinia 是什么关系
两者互补而非替代:
Pinia → 应用级(用户、权限、主题、跨页面共享)
Page Scope → 页面级(筛选、表格、弹窗、轮询、草稿)
ref / reactive → 组件级(输入框、局部 UI 态)
清晰划分这三层是复杂项目状态治理的关键。
演进:从 vue-page-store 到 vue-page-scope
从Vue 2时代的vue-page-store演进而来,核心改进:
vue-page-store (Vue 2) vue-page-scope (Vue 3)
────────────────────── ───────────────────────
"页面级 Store" "页面级 Scope"
hidden Vue instance effectScope(true)
hook:mounted 黑魔法 setup lifecycle hooks
bindTo(vm) 显式绑定 owner 模型自动绑定
$vm 逃生口 auto bridge + injection
当前进展
[email protected]已发布,主要特性:
- 完整的核心API体系
- 自动路由桥接与显式注入
- 跨版本插件协议
- 完善的TypeScript支持
- 双模块格式支持
- keep-alive优化与错误处理
npm install vue-page-scope
import { definePageScope, injectPageScope } from 'vue-page-scope'
Page Scope为复杂前端应用提供了优雅的状态管理方案,特别适合具有多弹窗、keep-alive等复杂交互场景的项目。通过明确的状态分层,它能有效解决Pinia store膨胀和状态泄漏问题,让前端架构更加清晰可维护。
相关文章
- 王者荣耀西施诗语江南价格是多少 06-02
- 《斗罗大陆魂师对决》焱魂骨如何选 哪个焱魂骨更值得培养 06-02
- 斗罗大陆魂师对决:焱辅助流魂环配置指南 焱辅助流派玩法全解析 06-02
- 斗罗大陆魂师对决:雷天魂环如何选择 哪个魂环更适合雷天 06-02
- Kimi智能助手有哪些功能 06-02
- 燕云十六声扶摇峰万事知任务怎么完成 06-02