状态管理与数据持久化
任务数据目前存在 App.vue 里,页面一多(列表页、统计页)就没法共享。本节引入 Pinia 集中管理状态,再用 localStorage + watch 实现"刷新页面数据不丢"的持久化。
安装并注册 Pinia
npm install pinia
在 main.js 中注册:
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
createApp(App).use(createPinia()).mount('#app')
创建任务仓库
新建 src/stores/task.js,用 defineStore 定义仓库:
// src/stores/task.js
import { defineStore } from 'pinia'
export const useTaskStore = defineStore('task', {
state: () => ({ // 状态:存放数据
tasks: [],
filter: 'all' // 筛选:all | active | done
}),
getters: { // 派生数据
doneCount: (state) => state.tasks.filter(t => t.done).length,
showTasks: (state) => {
if (state.filter === 'done') return state.tasks.filter(t => t.done)
if (state.filter === 'active') return state.tasks.filter(t => !t.done)
return state.tasks
}
},
actions: { // 动作:修改状态
addTask(title) {
this.tasks.push({ id: Date.now(), title, done: false })
},
removeTask(id) {
this.tasks = this.tasks.filter(t => t.id !== id)
},
toggleTask(id) {
const t = this.tasks.find(t => t.id === id)
if (t) t.done = !t.done
},
setFilter(f) {
this.filter = f
}
}
})
组件中使用仓库
任何组件调用 useTaskStore() 都能拿到同一份数据:
<script setup>
import { useTaskStore } from './stores/task'
const store = useTaskStore() // 全局唯一的数据源
store.addTask('学习 Pinia') // 调用 action 添加任务
console.log(store.tasks) // 输出:[{ id:…, title:'学习 Pinia', done:false }]
</script>
模板中直接渲染 store.showTasks,替代原先 props 层层传递的写法。
用 watch 实现自动持久化
在 store 文件末尾导出初始化函数:加载时先从本地恢复,之后每次变化自动保存。
// src/stores/task.js 末尾
import { watch } from 'vue'
export function initTaskPersistence() {
const store = useTaskStore()
// 页面加载时先从本地恢复
const saved = localStorage.getItem('taskhub-tasks')
if (saved) store.tasks = JSON.parse(saved)
// 之后每次变化都自动保存(deep 监听嵌套改动)
watch(
() => store.tasks,
(val) => localStorage.setItem('taskhub-tasks', JSON.stringify(val)),
{ deep: true }
)
}
在 main.js 中调用一次即可:
import { initTaskPersistence } from './stores/task'
initTaskPersistence()
效果:添加、删除、勾选后刷新浏览器,任务列表原样恢复,数据真正"留下来"了。
小结
Pinia 让全局数据共享变得简单,watch + localStorage 补上了持久化。数据层稳定后,下一节用 Vue Router 把"列表页"和"统计页"串起来。