一个小而完整的待办应用足以练习 Vue 3 的核心:响应式状态、派生数据、模板事件和浏览器持久化。本例不依赖第三方状态库,便于新手理解每一层职责。
一、创建项目
npm create vue@latest
cd your-project
npm install
npm run dev
在交互式选项中可先关闭 TypeScript、Router 和测试工具。浏览器打开终端给出的本地地址,确认欢迎页可见后再修改代码。
二、设计最小数据结构
每条任务只保存三个字段:唯一 id、文本 text、完成状态 done。把以下逻辑放入 App.vue 的 <script setup>:
import { computed, ref, watch } from 'vue'
const STORAGE_KEY = 'vue-todo-v1'
const input = ref('')
const filter = ref('all')
const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]'))
const visibleTodos = computed(() => {
if (filter.value === 'active') return todos.value.filter(todo => !todo.done)
if (filter.value === 'done') return todos.value.filter(todo => todo.done)
return todos.value
})
watch(todos, value => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(value))
}, { deep: true })
ref 保存会变化的状态;computed 只描述筛选结果;深度 watch 在任务或状态改变时写入本地存储。
三、添加、切换与删除任务
function addTodo() {
const text = input.value.trim()
if (!text) return
todos.value.push({ id: crypto.randomUUID(), text, done: false })
input.value = ''
}
function removeTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
模板可以保持语义化和简洁:
<form @submit.prevent="addTodo">
<label for="new-todo">新任务</label>
<input id="new-todo" v-model="input" maxlength="80">
<button type="submit">添加</button>
</form>
<button v-for="name in ['all', 'active', 'done']"
:key="name" @click="filter = name">
{{ name }}
</button>
<ul>
<li v-for="todo in visibleTodos" :key="todo.id">
<input v-model="todo.done" type="checkbox">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
四、逐项验证
- 输入只有空格时不应新增任务。
- 新增两条任务,勾选一条,筛选“未完成”和“已完成”。
- 刷新页面,任务和勾选状态应保留。
- 删除一条任务后再次刷新,被删除项不应恢复。
- 在开发者工具 Application → Local Storage 中检查
vue-todo-v1。
五、构建生产版本
npm run build
npm run preview
构建成功后用预览地址重新完成上述操作。localStorage 只属于当前浏览器和域名,不适合多设备同步,也不应保存密码或令牌。
常见问题
- 刷新后数据丢失:检查 key 是否一致,以及 JSON 是否被旧数据破坏。
- 筛选结果不更新:确认模板使用的是
visibleTodos。 - 列表状态错位:
v-for的:key应使用稳定 id,不能使用数组下标。