从命令式到声明式:用 Vue 3 构建任务清单的开发哲学
在前端开发演进的历程中,开发者逐渐从"操作 DOM"转向"管理状态"。一个看似简单的任务清单(Todos)应用,恰恰是理解这一范式转变的绝佳入口。本文将结合 Vue 3 的 Composition API、响应式系统与模板指令,深入剖析现代前端开发的核心思想------数据驱动 UI。
一、传统开发 vs Vue 开发:两种思维模式
在没有框架的时代,我们这样写任务输入功能:
ini
const app = document.getElementById('app');
const input = document.getElementById('todo-input');
input.addEventListener('change', (e) => {
const value = e.target.value.trim();
if (value) app.innerHTML = value;
});
这种方式称为 命令式编程:
- 先获取 DOM 元素
- 再监听事件
- 最后手动修改 DOM
问题显而易见:
- 代码与 DOM 强耦合
- 难以维护复杂交互
- 性能差(频繁操作慢速的渲染引擎)
而 Vue 的做法完全不同:
不再思考"页面元素怎么操作",而是思考"数据如何变化" 。
你只需定义:
- 当前标题是什么?
- 任务列表有哪些?
- 哪些任务已完成?
Vue 自动将这些数据映射到界面,并在数据变化时高效更新 DOM。这就是 声明式编程 的魅力。
二、Vue 3 Composition API:逻辑更清晰
在 App.vue 中,我们使用 <script setup> 语法,这是 Vue 3.2+ 推荐的组合式 API 写法:
php
import { ref, computed } from 'vue';
const title = ref(""); // 响应式字符串
const todos = ref([
{ id: 1, title: '打王者', done: false },
{ id: 2, title: '吃饭', done: true }
]);
ref()将普通值包装为响应式对象。- 所有逻辑集中在
setup中,避免 Options API 的碎片化。
相比 Vue 2 的 data()、methods、computed 分散在不同选项,Composition API 让相关功能内聚,便于复用与测试。
三、核心指令:用声明式语法描述 UI
Vue 的模板通过指令(v- 开头)实现声明式渲染:
1. v-model:双向绑定
ini
<input v-model="title" @keydown.enter="addTodo">
<input type="checkbox" v-model="todo.done">
- 输入框内容 ↔
title - 复选框状态 ↔
todo.done
无需手动监听 input 或 change 事件。
2. v-for:列表渲染
ini
<li v-for="todo in todos" :key="todo.id">
- 自动遍历
todos数组 :key提供唯一标识,提升 Diff 算法效率
3. v-if / v-else:条件渲染
xml
<ul v-if="todos.length">...</ul>
<div v-else>暂无计划</div>
- 根据数据是否存在决定显示内容
4. 动态 class 绑定
css
<span :class="{ done: todo.done }">{{ todo.title }}</span>
- 若
done为真,应用.done样式(灰色 + 删除线)
四、计算属性:智能派生状态
任务清单需要实时显示"未完成任务数"和"全选"功能。Vue 用 computed 完美解决:
1. 只读计算属性:未完成数量
ini
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length;
});
- 自动依赖
todos - 结果缓存,仅在
todos变化时重新计算 - 模板中直接使用
{{ active }}
2. 可读写计算属性:全选控制
ini
const allDone = computed({
get() {
return todos.value.every(todo => todo.done);
},
set(val) {
todos.value.forEach(todo => todo.done = val);
}
});
- get:判断是否全部完成
- set:当用户点击"全选"复选框时,批量更新所有任务状态
这体现了 Vue 对"属性"的扩展------不仅是值,还可以包含行为。
五、事件处理:简洁而高效
添加任务只需一行逻辑:
ini
const addTodo = () => {
if (!title.value) return;
todos.value.push({
id: Math.random(),
title: title.value,
done: false
});
title.value = ''; // 清空输入框
};
配合 @keydown.enter,用户按回车即可提交,体验流畅。
六、开发哲学:聚焦数据,而非 DOM
正如项目注释所言:
"VUE 做法不再需要思考页面的元素怎么操作,而是要思考数据是怎么变化的。"
这种转变带来三大优势:
- 开发效率高:无需手动操作 DOM,减少样板代码
- 可维护性强:逻辑围绕数据组织,结构清晰
- 性能更优:Vue 内部使用虚拟 DOM 和响应式系统,自动优化更新
对新手友好,对老手高效------这正是 Vue "渐进式框架"理念的体现。
七、结语
一个简单的任务清单,浓缩了 Vue 3 的精华:
- 响应式系统:数据变,视图自动更新
- Composition API:逻辑聚合,易于复用
- 声明式模板:直观表达 UI 与状态关系
- 计算属性:智能、缓存、响应式的派生数据
它不仅是一个功能 demo,更是一种思维方式的启蒙:前端开发的本质,是管理状态,而非操作元素。
未来,你可以在此基础上扩展:
- 本地存储(localStorage)
- 过滤视图(全部/未完成/已完成)
- 动画过渡
- TypeScript 类型支持
而这一切,都始于对"数据如何流动"的思考。
附录:完整的App.vue代码
vue
<template>
<div>
<h1>任务清单</h1>
<h2>{{ title }}</h2>
<input type="text" v-model="title" @keydown.enter="addTodo">
<ul v-if="todos.length">
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{done: todo.done}">{{ todo.title }}</span>
</li>
</ul>
<div v-else>
暂无计划
</div>
<div>
全选<input type="checkbox" v-model="allDone">
{{ active }}
/
{{ todos.length }}
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const title = ref("")
const todos = ref([
{
id: 1,
title: '打王者',
done: false
},
{
id: 2,
title: '吃饭',
done: true
}
])
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
const addTodo = () => {
if (!title.value) return;
todos.value.push({
id: Math.random,
title: title.value,
done: false
})
title.value = ''
}
const allDone = computed({
get() {
return todos.value.every(todo => todo.done)
},
set(val) {
todos.value.forEach(todo => todo.done = val)
}
})
</script>
<style>
.done {
color: gray;
text-decoration: line-through;
}
</style>