computed 追踪依赖
需求背景
现在有一个需求,需要展示状态中存储的任务总数,状态中存储的任务列表是一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { defineStore } from 'pinia' import { nanoid } from 'nanoid' import type { Task } from '@/types/task'
export const useTaskStore = defineStore('task', { state: () => ({ tasks: [] as Task[] }), actions: { addTask(title: string, description = '') { this.tasks.push({ id: nanoid(), title, description, completed: false, createdAt: new Date().toISOString() }) }, toggleTask(id: string) { const task = this.tasks.find(t => t.id === id) if (task) task.completed = !task.completed }, deleteTask(id: string) { this.tasks = this.tasks.filter(t => t.id !== id) }, removeCompletedTasks(){ this.tasks = this.tasks.filter(t => t.completed === false) } }, persist: true })
|
错误示范
在展示的时候,很自然的写出了这样的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div style="padding: 2rem"> <h1>Taskmate</h1> <n-space justify="start"> <n-button-group> <n-button round type="tertiary"> {{ totalTaskCount > 0 ? "今日任务总数:" + totalTaskCount : '当前尚无任务'}} </n-button> </n-button-group> </n-space>
</div> </template>
<script setup lang="ts"> import {ref} from 'vue' import {useTaskStore} from '@/stores/taskStore'
const taskStore = useTaskStore()
const totalTaskCount = ref(0)
totalTaskCount.value = taskStore?.tasks?.length;
</script>
|
但是后续测试发现,如果修改了 pinia 中的数组长度,界面上展示的个数并不会发生改变,这主要是因为这种方式本质上只是一个快照
totalTaskCount.value = taskStore.tasks.length
是一段同步代码,只会在组件初始化的时候时执行一次;
taskStore.tasks
虽然是响应式的,但你只把它的值赋值了一次,而不是建立一个依赖关系;
所以后续 tasks
增减变化了,totalTaskCount.value
也不会自动变化。
所以上述装模做样的定义了一个 totalTaskCount
本质上只是拍了一个快照,等价于:
1
| let snapshot = store.value.length
|
每次渲染时调用
后续我换成了这样的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div style="padding: 2rem"> <h1>Taskmate</h1> <n-space justify="start"> <n-button-group> <n-button round type="tertiary"> {{ getTotalTaskCount() > 0 ? "今日任务总数:" + getTotalTaskCount() : '当前尚无任务'}} </n-button> </n-button-group> </n-space>
</div> </template>
<script setup lang="ts"> import {ref} from 'vue' import {useTaskStore} from '@/stores/taskStore'
const taskStore = useTaskStore()
function getTotalTaskCount(){ return taskStore?.tasks?.length; }
</script>
|
这样的方式可以解决问题
本质上其实是每次渲染都会调用 getTotalTaskCount()
,而 taskStore.tasks
是响应式数据,模板中的函数调用是可以自动响应依赖更新的。(在 Vue 3 中,模板本质是会被编译为 render()
函数,而 render()
函数内部任何访问到的响应式数据,Vue 都会自动追踪它的依赖。)
因此一旦任务变化:
taskStore.tasks
变化被 Vue 侦测到;
- 触发响应式更新机制;
- 重新调用
render
;
- 所以按钮上的任务数量就能更新。
computed最佳实践
其实最佳实践思路是利用 Vue3 组合式 API 的 computed 函数来实现追踪依赖
我们现在是希望某个变量能够追踪 taskStore.task 这个响应式状态的,因此可以这么定义
const totalTaskCount = computed(() => taskStore.tasks.length)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div style="padding: 2rem"> <h1>Taskmate</h1> <n-space justify="start"> <n-button-group> <n-button round type="tertiary"> {{ totalTaskCount > 0 ? "今日任务总数:" + totalTaskCount : '当前尚无任务'}} </n-button> </n-button-group> </n-space>
</div> </template>
<script setup lang="ts"> import {ref} from 'vue' import {useTaskStore} from '@/stores/taskStore'
const taskStore = useTaskStore()
const totalTaskCount = computed(() => taskStore.tasks.length)
</script>
|
通过这样的写法,当任务变化,totalTaskCount
会自动更新
循环渲染组件
在我们首页需要遍历 taskStore 中数组渲染所有任务的时候,这个需求可以很完美的使用 v-for
进行解决
1 2 3 4 5 6 7 8 9
| <n-space vertical :size="16" style="margin: 1rem 0"> <TaskItem v-for="task in filteredData" :key="task.id" :task="task" @edit="openEdit" @delete="deleteTask" /> </n-space>
|
这里 v-for
是用于遍历数组或对象,渲染多个组件
:key
是性能优化和组件 diff 的关键,必须是唯一值(如 task.id
)。