📝在现代前端开发中,Vue.js 凭借其简洁的语法、强大的响应式系统和灵活的组合式 API,成为构建用户界面的首选框架之一。本文将围绕一个典型的 Todos 任务清单 应用,深入剖析 Vue 3 的核心概念与最佳实践,并结合源码中的详尽中文注释,逐层揭示其设计思想与实现细节。
作者有话说:Vue3-todos 说它想插队,所以我发了~
点赞、评论、收藏支持一波Vue3-todos吧~
零、🛠️ 项目初始化——从零开始搭建 Vue 3 + Vite 项目
要运行本文中的 Todos 应用,首先需要创建一个 Vue 3 项目。推荐使用官方脚手架工具 Vite,它具有极快的启动速度和现代化的开发体验。
1. 使用 npm init vite 创建项目
bash
npm init vite
或使用:
bash
编辑
npx create-vite
这将启动一个交互式引导流程,帮助你选择框架和语言:
text
编辑
Project name: vue3-todos
Select a framework: Vue
Select a variant: JavaScript
Use rollup-vite (Experimental)? No
Install with npm and start now? Yes
2. 进入项目并安装依赖
项目创建完成后,进入目录并安装依赖:
bash
编辑
cd vue3-todos
npm install
3. 启动开发服务器
bash
编辑
npm run dev
浏览器会自动打开 http://localhost:5173,即可看到你的Vue 项目!
一、👀 效果预览 —— 让我们先去 Vue3-todos 先瞅一瞅
🔗你也可以通过以下链接查看:
二、🧠 核心思想:数据驱动视图
传统开发中,我们常常需要手动查找 DOM 元素、监听事件、再更新内容——这种方式不仅繁琐,还容易引发状态不一致的问题。而现代响应式框架的核心理念是:
“你只需关心数据如何变化,界面会自动随之更新。”
这一思想在代码注释中被清晰表达:
“不再需要思考页面的元素怎么操作,而是要思考数据是怎么变化的。”
“聚焦的是数据业务逻辑,而不是页面元素操作。”
换句话说,开发者只需维护一份“真实状态”(如任务列表、输入内容),框架会自动将这份状态映射到界面上。这种声明式编程范式,极大提升了开发效率与代码可维护性。
在没有现代前端框架的时代,开发者必须通过原生 JavaScript 直接操作 DOM 元素来实现交互。例如,实现一个简单的任务输入功能:
<h2 id="app"></h2>
<input type="text" id="todo-input">
<script>
const app = document.getElementById('app');
const todoInput = document.getElementById('todo-input');
todoInput.addEventListener('change', function(event) {
const todo = event.target.value.trim();
if (!todo) {
console.log('请输入任务');
return;
}
app.innerHTML = todo; // 手动更新 DOM
});
</script>
这种方式存在明显缺陷:
- 命令式逻辑:每一步都要明确“做什么”(找元素 → 监听事件 → 修改内容)。
- 性能瓶颈:频繁操作真实 DOM 会触发浏览器重排重绘,效率低下。
- 可维护性差:当功能复杂(如增删改查、筛选、统计)时,代码迅速变得混乱。
- 状态管理困难:数据分散在 DOM 和变量中,难以统一追踪。
本质上,这种开发模式是 “以 DOM 为中心” 的,开发者需要时刻关注页面结构如何变化。
Vue 彻底改变了这一范式。它的核心哲学是:
“你不需要关心页面如何更新,只需要描述数据应该是什么样子。”
在 Vue 中,视图是数据的映射。只要数据变了,框架会自动、高效地更新对应的 DOM。开发者只需专注于 业务逻辑和数据流,而无需手动操作元素。
这种 “以数据为中心” 的声明式编程,不仅提升了开发效率,也大幅降低了出错概率。尤其对初学者而言,Vue 能让他们写出结构清晰、逻辑严谨的代码,避免陷入 DOM 操作的泥潭。
三、🎯 应用功能概览
该任务清单应用实现了以下典型功能:
- 动态显示当前输入标题
- 回车提交新任务(支持空值过滤)
- 渲染可勾选的任务列表
- 支持全选/取消全选
- 实时统计未完成任务数量
- 空状态友好提示
- 视觉反馈(已完成任务置灰并加删除线)
- 响应式布局与美观背景
所有这些功能,均通过响应式数据流与声明式模板实现,无任何直接 DOM 操作。
四、🔌 响应式数据:ref
// 响应式数据
const title = ref("")
const todos = ref([ /* ... */ ])
✅ 知识点详解:
ref是创建响应式引用的基础工具。- 注释明确指出这是 “响应式数据”,意味着对它的修改会自动触发视图更新。
- 对于字符串、数字等基本类型,必须使用
ref才能获得响应性。 - 在
<script setup>中,内部通过.value访问;模板中则自动解包,可直接使用变量名。 - 数组或对象包裹在
ref中后,其内部属性依然保持响应性,得益于 Vue 底层的 Proxy 机制。
💡 注释还强调: “业务式页面上要动态展示标题”
这说明变量不仅是存储容器,更是业务状态的一部分。
ref 用于包裹基本类型(如字符串、数字)或对象。
在 <script setup> 中,ref 创建的变量在模板中可直接使用(无需 .value),但在逻辑中访问/修改需通过 .value。
例如:title.value = '' 清空输入内容,Vue 会自动更新绑定的输入框。
五、✍️ 双向绑定:v-model
<!-- 双向数据绑定 -->
<input type="text" v-model="title" @keydown.enter="addTodo">
✅ 知识点详解:
v-model是 Vue 提供的语法糖,用于在表单控件上建立双向数据绑定。- 注释解释其优势:
“不用addEventListener 事件绑定”
- 它替代了传统的
input.addEventListener('input', handler)手动监听方式。 - 在 Vue 3 中,
v-model默认监听input事件,并智能处理中文输入法(在 composition 结束后才更新),避免中间状态干扰。
六、⌨️ 事件处理:@keydown.enter
<!-- @event-name:enter 监听键盘输入,当按下回车的时候 -->
<input type="text" v-model="title" @keydown.enter="addTodo">
✅ 知识点详解:
@是v-on:的缩写,用于绑定事件监听器。- 注释精准描述了其作用:
“@event-name:enter 监听键盘输入,当按下回车的时候”
.enter是键盘事件修饰符,仅在用户按下 Enter 键时触发回调函数。- 这种设计提升了交互体验,无需额外按钮即可快速添加任务。
这是 Vue 对 addEventListener 的语法糖,更简洁直观。
七、➕ 新增任务:业务逻辑聚焦
// 新增任务
const addTodo = () => {
// focus 数据业务
// 检查标题是否为空或只包含空格
if(!title.value.trim()) return;
todos.value.push({ id: Math.random(), title: title.value, done: false });
title.value = ''; // 清空输入框
}
✅ 知识点详解:
- 注释强调:
“focus 数据业务” 表明该函数的核心是维护数据状态,而非操作 DOM。
- 使用
trim()过滤纯空格输入,防止无效任务入库。 - 虽然
Math.random()作为 ID 在生产环境中不够严谨,但在演示场景中足够轻量。 - 清空
title.value后,因v-model的双向绑定特性,输入框自动清空——再次体现“改数据即改视图”。
在更规范的实践中,可使用 Date.now() 作为简易 ID 生成方案,例如:
id: Date.now()
这比 Math.random() 更具时间顺序性和唯一性保障。
八、📋 列表渲染:v-for 与唯一键
<!-- 任务列表 -->
<!-- v-if 条件渲染 满足条件渲染 不满足条件不渲染 -->
<ul v-if="todos.length">
<!-- key 唯一属性 -->
<li v-for="todo in todos" :key="todo.id">
✅ 知识点详解:
- 注释明确指出:
“key 唯一属性”
v-for必须配合唯一key,以帮助框架高效识别节点身份,实现最小化 DOM 更新。- 若使用数组索引作为 key,在列表发生插入、删除等操作时,可能导致组件状态错乱(如复选框选中状态错位)。
- 注释还说明:
“v-if 条件渲染 满足条件渲染 不满足条件不渲染” 强调
v-if是“真·条件渲染”——当条件为假时,元素不会存在于 DOM 中。
九、✅ 任务完成状态:嵌套响应式绑定
<input type="checkbox" v-model="todo.done">
✅ 知识点详解:
- 每个复选框直接绑定到对应任务的
done属性。 - 由于整个
todos数组是响应式的,修改任一任务的done状态会立即触发局部 UI 更新。 - 这体现了 Vue 的细粒度响应式更新能力——只重绘变化的部分,而非整个列表。
十、🎨 动态样式::class 与语义化表达
<!-- v-bind:缩写 js 表达式
vue 有一定的学习 api 对用户非常友好 好上手 -->
<span :class="{'done': todo.done}">{{ todo.title}}</span>
✅ 知识点详解:
- 注释解释了语法:
“v-bind:缩写 js 表达式” 即
:class是v-bind:class的简写形式。 - 使用对象语法
{ 'done': todo.done },当任务完成时自动添加done类。 - 注释还评价:
“vue 有一定的学习 api 对用户非常友好 好上手” 说明框架在降低学习成本上的用心设计。
- 对应的 CSS 规则:
通过视觉反馈直观传达任务状态。/* 已完成任务样式 */ .done { color: gray; text-decoration: line-through; }
配合 CSS 可实现更友好的用户体验:
.done {
color: #888;
text-decoration: line-through;
}
十一、❌ 条件渲染:空状态处理
<ul v-if="todos.length"> ... </ul>
<!-- v-else 没有v-if 渲染 -->
<div v-else>
暂无计划
</div>
✅ 知识点详解:
- 注释说明:
“v-else 没有v-if 渲染” 即当任务列表为空时,显示友好的提示信息。
v-else必须紧跟在v-if或v-else-if之后,否则无效。- 这种模式提供了良好的用户体验,避免空白界面带来的困惑。
空状态提示可进一步优化文案,例如:
📝 暂无计划,快添加你的第一个任务吧!
十二、📊 计算属性:性能与语义的统一
1. 未完成任务数量:active
// 依赖于todos 响应式数据的 计算属性
// 形式上是函数(计算过程),结果(计算属性)返回
// 也是响应式的 依赖于todos
// computed 缓存 性能优化 只有 todos 变化时才会重新计算
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
✅ 知识点详解:
- 注释完整阐述了
computed的核心价值:“依赖于todos 响应式数据的 计算属性”
“形式上是函数(计算过程),结果(计算属性)返回”
“也是响应式的 依赖于todos”
“computed 缓存 性能优化 只有 todos 变化时才会重新计算” - 相比在模板中直接写
{{ todos.filter(...).length }},使用computed避免了每次渲染都重复执行过滤逻辑。 - 它具有缓存机制:只要
todos未变,多次访问active不会重新计算。 - 同时,命名
active使代码更具语义性,提升可读性。
computed 是“计算属性”,本质是一个带缓存的函数。
优势:只有当依赖的数据(如 todos)发生变化时,才会重新计算。避免不必要的重复运算。
在本例中,active 实时反映未完成任务数,但不会在每次渲染时都执行 filter。
2. 全选控制:可写计算属性
// computed 高级技巧
// get set 属性的概念
const allDone = computed({
get(){
return todos.value.every(todo => todo.done)
},
set(val){
todos.value.forEach(todo => todo.done = val)
}
})
✅ 知识点详解:
- 注释点明这是:
“computed 高级技巧”
“get set 属性的概念” get函数判断是否所有任务都已完成(使用Array.every)。set函数接收布尔值val,批量设置所有任务的完成状态。- 将其绑定到复选框:
即可实现双向同步:点击全选 → 所有任务完成;取消任一任务 → 全选自动取消。<input type="checkbox" v-model="allDone"> - 这种模式避免了额外的状态变量和监听逻辑,是处理聚合状态的经典方案。
更健壮的 get 实现应考虑空列表情况:
get() {
return todos.value.length > 0 && todos.value.every(todo => todo.done);
}
避免在无任务时误判为“全选”。
十三、🎨 样式与布局:组件级封装与视觉优化
<style scoped>
/* 添加页面背景 - 仅应用于根容器 */
.app-container {
/* 使用 assets 目录下的图片 作为背景图片 */
background-image: url('./assets/image5.png');
background-size: cover;
min-height: 100vh;
/* ... */
}
/* 为内容添加半透明背景,提高可读性 */
h2, input, ul, div:last-of-type {
background-color: rgba(255, 255, 255, 0.8);
/* ... */
}
</style>
✅ 知识点详解:
<style scoped>:确保样式仅作用于当前组件,避免全局污染。这是组件化开发的重要保障。- 注释说明背景图用途:
“使用 assets 目录下的图片 作为背景图片”
“添加页面背景 - 仅应用于根容器” background-size: cover保证图片完整覆盖容器且不失真。- 半透明遮罩注释:
“为内容添加半透明背景,提高可读性” 这是 UI 设计中的常见技巧,尤其适用于复杂或深色背景。
min-height: 100vh确保即使任务为空,页面也占满整个视口高度,提升视觉完整性。
通过简单的 CSS,我们让完成的任务呈现删除线效果,界面更加直观友好。scoped 属性确保样式仅作用于当前组件,避免全局污染。
可进一步增强底部统计区域的样式:
.stats {
margin-top: 16px;
font-size: 14px;
color: #666;
}
input[type="text"] {
padding: 8px;
width: 100%;
margin-bottom: 12px;
border: 1px solid #ddd;
border-radius: 4px;
}
十四、🧠 开发范式演进:从选项式到组合式
// setup vue3 composition 组合式 API
// vue2 options API
import {ref, computed} from 'vue'
✅ 知识点详解:
- 注释对比了两种范式:
“setup vue3 composition 组合式 API”
“vue2 options API” - 组合式 API(Composition API) 允许按逻辑关注点组织代码。例如,所有与“任务列表”相关的数据、计算属性、方法可以放在一起,而非分散在
data、computed、methods等选项中。 <script setup>是 Vue 3 的编译时语法糖,进一步减少样板代码,提升开发体验。- 这种组织方式更利于逻辑复用(通过自定义 Hook)和 TypeScript 类型推导。
为什么 Vue 特别适合初学者?
- 低门槛上手:模板语法贴近 HTML,指令命名直观(
v-if就是“如果”),无需深入理解虚拟 DOM 或响应式原理即可开发。 - 自动状态同步:告别
getElementById和innerHTML,数据即视图。 - 组合式逻辑组织:Composition API 允许将相关逻辑(如“任务增删”、“全选控制”)集中编写,比 Options API 更灵活。
- 内置最佳实践:如
key提示、computed缓存、事件修饰符等,引导开发者写出高性能代码。 - 渐进式框架:可从简单组件开始,逐步引入路由、状态管理等高级功能。
十五、💻 完整实现代码
<template>
<div class="app-container">
<h1>Todos 任务清单</h1>
<!-- 数据绑定 -->
<h2>{{ title }}</h2>
<!-- 双向数据绑定 -->
<!-- @ v-bind: 缩写 不用addEventListener 事件绑定 -->
<!-- @event-name:enter 监听键盘输入,当按下回车的时候 -->
<input type="text" v-model="title" @keydown.enter="addTodo">
<!-- 任务列表 -->
<!-- v-if 条件渲染 满足条件渲染 不满足条件不渲染 -->
<ul v-if="todos.length">
<!-- key 唯一属性 -->
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<!-- v-bind:缩写 js 表达式
vue 有一定的学习 api 对用户非常友好 好上手 -->
<span :class="{'done': todo.done}">{{ todo.title}}</span>
</li>
</ul>
<!-- v-else 没有v-if 渲染 -->
<div v-else>
📝 暂无计划,快添加你的第一个任务吧!
</div>
<div class="stats">
<!-- 全选复选框 -->
全选 <input type="checkbox" v-model="allDone">
<!-- {{ 计算之后的数据绑定 表达式结果绑定}} -->
<!-- 展示未完成的任务数量 -->
<!-- {{todos.filter(todo => !todo.done).length}} -->
<!-- 数据表达式 能完成功能 但是直接输出 是个计算过程 一定会再执行 当页面更新之后这个表达式一定会再次更新 重新计算 重新渲染 -->
{{ active }}
/
{{ todos.length }} 项未完成
</div>
</div>
</template>
<script setup>
// 业务式页面上要动态展示标题
// vue focus 标题数据业务,修改数据,余下的dom 更新vue 替我们做了
// setup vue3 composition 组合式 API
// vue2 options API
// import { ref, onMounted } from 'vue'
import {ref, computed} from 'vue'
// 响应式数据
const title = ref("")
const todos = ref([
{
id: 1,
title: '打游戏',
done: false
},
{
id: 2,
title: '吃饭',
done: false
},
{
id: 3,
title: '睡觉',
done: false
},
{
id: 4,
title: '学习Vue',
done: false
}
])
// 依赖于todos 响应式数据的 计算属性
// 形式上是函数(计算过程),结果(计算属性)返回
// 也是响应式的 依赖于todos
// computed 缓存 性能优化 只有 todos 变化时才会重新计算
const active = computed(() => {
return todos.value.filter(todo => !todo.done).length
})
// 新增任务
const addTodo = () => {
// focus 数据业务
// 检查标题是否为空或只包含空格
if(!title.value.trim()) return;
todos.value.push({
id: Date.now(),
title: title.value,
done: false
}); // 新增任务
title.value = ''; // 清空输入框
}
// computed 高级技巧
// get set 属性的概念
const allDone = computed({
get(){
return todos.value.length > 0 && todos.value.every(todo => todo.done)
},
set(val){
todos.value.forEach(todo => todo.done = val)
}
})
</script>
<style scoped>
/* 添加页面背景 - 仅应用于根容器 */
.app-container {
/* 使用 assets 目录下的图片 作为背景图片 */
background-image: url('./assets/image5.png');
/* 背景图片设置 */
background-size: cover; /* 图片覆盖整个容器 */
background-repeat: no-repeat; /* 不重复 */
background-position: center; /* 居中显示 */
min-height: 100vh; /* 至少占满整个视口高度 */
width: 100%; /* 确保占满整个视口宽度 */
padding: 20px;
box-sizing: border-box;
}
/* 为内容添加半透明背景,提高可读性 */
h2, input, ul, .stats {
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
margin: 5px 0;
}
/* 已完成任务样式 */
.done{
color: #888;
text-decoration: line-through;
}
.stats {
margin-top: 16px;
font-size: 14px;
color: #666;
}
input[type="text"] {
padding: 8px;
width: 100%;
margin-bottom: 12px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
十六、🚀 总结
这个看似简单的任务清单应用,实则完整展示了现代前端框架的核心能力:
- ✅ 响应式系统 → “响应式数据”
- ✅ 声明式渲染 → “v-if 条件渲染”、“v-bind:缩写”
- ✅ 计算属性缓存 → “computed 缓存 性能优化”
- ✅ 事件修饰符 → “@event-name:enter 监听键盘输入”
- ✅ 组件级样式封装 → “仅应用于根容器”
- ✅ 组合式逻辑组织 → “setup vue3 composition”
更重要的是,它始终贯彻一个原则:
“你只需聚焦数据如何变化,其余交给框架。”
正如注释所言:
“修改数据,余下的 DOM 更新 Vue 替我们做了。”
这正是响应式编程的魅力所在——让开发者从繁琐的界面同步中解放出来,专注于业务本身。
通过这个 Todos 示例,我们完成了从前端开发范式的根本转变:
- 过去:思考“如何找到元素并修改它”。
- 现在:思考“数据应该变成什么样子”。
Vue 的响应式系统就像一位智能助手,默默监听你的数据变化,并精准、高效地更新页面。你只需专注于业务规则:
“当用户按下回车,就把输入内容加入任务列表。”
“全选框的状态取决于所有任务是否完成。”
这种 声明式 + 响应式 的开发模式,不仅提升了生产力,也让代码更具可读性和可维护性。
对于初学者而言,掌握 Vue 的核心不是记住 API,而是建立 数据驱动视图 的思维方式。一旦理解这一点,无论是开发 Todos、购物车还是复杂后台系统,都能游刃有余。
记住:在 Vue 的世界里,数据是国王,视图只是它的影子。
动手实践这个 Todos 应用吧!它是通往现代前端开发的重要第一步。