前言
本文将带你从一个Todos清单组件,上手Vue基础语法以及核心思想,设计内容有ref响应式数据,v-指令、computed计算属性
完整代码:
js
<!-- App.vue -->
<script setup>
import { ref, computed } from 'vue'
// 1. 响应式数据:所有 UI 都靠它们驱动
const title = ref('') // 输入框内容
const todos = ref([
{ id: 1, title: '睡觉', done: true },
{ id: 2, title: '吃饭', done: true }
])
// 添加任务
function addTodo() {
if (!title.value.trim()) return
todos.value.push({
id: Math.random(),
title: title.value.trim(),
done: false
})
title.value = '' // 清空输入框
}
// 2. 计算属性:派生数据,自动更新
// 统计未完成的数量
const active = computed(() => {
return todos.value.filter(t => !t.done).length
})
// 全选/全不选的高级玩法
const allDone = computed({
get() {
return todos.value.length > 0 && todos.value.every(t => t.done)
},
set(val) {
todos.value.forEach(t => t.done = val)
}
})
</script>
<template>
<div class="container">
<h2>我的 Todos 任务清单</h2>
<!-- 双向绑定 + 回车添加 -->
<input type="text" placeholder="今天还要干啥?回车添加" 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 class="empty">暂无计划,摸鱼去吧~</div>
<!-- 统计 + 全选 -->
<div class="footer">
<label>
<input type="checkbox" v-model="allDone" />
全选
</label>
<span>未完成 {{ active }} / 共 {{ todos.length }} 条</span>
</div>
</div>
</template>
<style scoped>
.container {
max-width: 400px;
margin: 40px auto;
font-family: sans-serif;
}
input[type="text"] {
width: 100%;
padding: 8px;
font-size: 16px;
}
ul {
padding-left: 0;
list-style: none;
}
li {
padding: 8px 0;
display: flex;
align-items: center;
gap: 8px;
}
.done {
color: #999;
text-decoration: line-through;
}
.empty {
color: #999;
text-align: center;
padding: 20px;
}
.footer {
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
实际展示:

一、拆解Vue代码
其实Vue主界面的代码就是我们的HTML+JS+CSS,它是一个三明治结构
javascript
<script setup></script> //JavaScript
<template></template> //HTML
<style scoped></style> //CSS
二、 响应式数据:Vue 的灵魂
传统写法(jQuery 时代):
JavaScript
// 想改标题?先找 DOM 再改 innerText
document.querySelector('h2').innerText = '新标题'
Vue 写法:
JavaScript
const title = ref('Todos任务清单')
然后模板里直接 {{ title }},你改 title.value = '新标题',页面就自动变了。
为什么这么神奇?因为 Vue 把 todos、title 这些 ref 包装成了响应式对象,只要你通过 .value 去改它,Vue 就知道「有数据变了」,然后自动把所有用到这个数据的地方更新一遍。
记住一句话:在 Vue 里,你只需要操心数据怎么变,页面怎么变 Vue 帮你搞定。
三、 Vue 基础指令
| 指令 | 作用 | 本例中的用法 |
|---|---|---|
| v-model | 双向绑定,表单专属神器 | 输入框和任务的 checkbox 都用了它 |
| v-for | 循环渲染数组 | 遍历 todos 渲染每一行 |
| v-if / v-else | 条件渲染 | 没任务时显示「暂无计划」 |
| v-bind | 动态绑定HTML属或者组件的props | 完成的任务加上删除线和灰色 |
| @click / @keydown.enter | 事件监听简写 | 回车添加任务 |
这些指令就是 Vue 给我们提供的「魔法语法糖」,让我们几乎不用写原生 DOM 操作。
1. v-model ------ 双向数据绑定
html
<input type="text" v-model="title" @keydown.enter="addTodo" />
<input type="checkbox" v-model="todo.done">
<input type="checkbox" v-model="allDone" />
🔍 作用:
-
自动同步表单输入和响应式数据。
-
对于
<input type="text">:
v-model="title"等价于:html:value="title" @input="title = $event.target.value"用户输入 →
title.value自动更新;title.value变化 → 输入框内容更新。 -
对于
<input type="checkbox">:
v-model="todo.done"绑定的是布尔值:html:checked="todo.done" @change="todo.done = $event.target.checked" -
对于
allDone(计算属性):因为
allDone有get和set,所以v-model能读也能写,实现"全选"联动。
🔄 2. v-for ------ 列表渲染
html
<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>
🔍 作用:
- 遍历数组(或对象),为每个元素生成一个 DOM 节点。
todo in todos:todos是源数组,todo是当前项。- 必须加
:key:Vue 用key来高效追踪每个节点的身份(避免复用错误)。你用了todo.id,很好!
⚠️ 注意:
- 不要使用
index作为key(除非列表只增不删),否则可能引发状态错乱。 v-for的优先级高于v-if(如果同时用),但一般建议避免两者同用。
3. v-if 与 v-else ------ 条件渲染
html
<ul v-if="todos.length">
<!-- 渲染任务列表 -->
</ul>
<div v-else class="empty">暂无计划,摸鱼去吧~</div>
🔍 作用:
v-if="todos.length":当todos.length > 0(真值)时,渲染<ul>。v-else:必须紧跟在v-if或v-else-if后面,表示"否则"。- 完全销毁/重建 DOM(不是隐藏),适合切换不频繁的场景。
💡 对比 v-show:
v-show是通过 CSSdisplay: none切换,DOM 始终存在。- 你的场景用
v-if/v-else更合适,因为"空状态"和"有任务"是互斥的两种 UI。
4. v-bind(简写 :)------ 动态绑定属性
html
<span :class="{ done: todo.done }">{{ todo.title }}</span>
<li v-for="todo in todos" :key="todo.id">
🔍 作用:
- 将 HTML 属性(如
class、id、src、key等)绑定到 JS 表达式。 :class="{ done: todo.done }":
当todo.done === true时,给<span>添加class="done",从而应用样式(删除线+灰色)。:key="todo.id":将key属性绑定到todo.id的值。
💡 其他常见用法:
html
<img :src="imageUrl" />
<div :style="{ color: textColor }"></div>
<a :href="linkUrl">链接</a>
v-bind是 Vue 实现"数据驱动视图"的关键桥梁。
5. @(即 v-on)------ 事件监听
✅ 在你的代码中:
html
<input ... @keydown.enter="addTodo" />
🔍 作用:
-
监听 DOM 事件,并执行方法。
-
@keydown.enter是修饰符写法,等价于:js@keydown="(e) => { if (e.key === 'Enter') addTodo() }" -
常见修饰符:
.enter:回车键.stop:阻止事件冒泡.prevent:阻止默认行为.once:只触发一次
💡 其他例子:
html
<button @click="deleteTodo(id)">删除</button>
<input @input="handleInput" />
<form @submit.prevent="handleSubmit">...</form>
@是v-on:的缩写,就像:是v-bind:的缩写。
总结:
-
用户在输入框输入 →
v-model同步到title -
按回车 →
@keydown.enter触发addTodo()→ 新任务加入todos -
v-for重新遍历todos,渲染新<li> -
每个任务的复选框用
v-model双向绑定todo.done -
勾选复选框 →
todo.done更新 →active计算属性自动更新(未完成数)allDone.get()重新计算(是否全选)
-
底部全选框用
v-model="allDone"→ 点击时调用allDone.set()→ 批量更新所有todo.done -
如果
todos为空 →v-if切换到v-else显示"摸鱼"提示
四、 computed 计算属性:自动缓存的派生数据

我们想显示「未完成的任务有几条」,最笨的写法是:
Vue
{{ todos.filter(t => !t.done).length }} / {{ todos.length }}
这样每次渲染都会重新 filter 一遍,数据一大就废。
聪明做法:
JavaScript
const active = computed(() =>
todos.value.filter(t => !t.done).length
)
computed 有两大杀招:
- 缓存:只有当 todos 真的变了,它才会重新计算
- 响应式:todos 一变,页面上所有用了 active 的地方立刻更新
更牛的全选 checkbox:

JavaScript
const allDone = computed({
get() {
return todos.value.length > 0 && todos.value.every(t => t.done)
},
set(val) {
todos.value.forEach(t => t.done = val)
}
})
这样 就能实现「全选/全不选」双向功能,代码量少到令人发指。
在 Vue 3 的 computed 中,get 和 set 是用于定义「可读写计算属性」的两个函数 。默认情况下,computed 只有一个 getter(即只读),但当你需要让计算属性也能被赋值(比如用在 v-model 中),就需要提供一个 setter(即 set 函数)。
JavaScript
const myComputed = computed({
get() {
// 返回派生值(必须)
return someDerivedValue
},
set(newValue) {
// 处理赋值逻辑(可选)
// 根据 newValue 更新原始数据
}
})
get() ------ 读取时触发
- 当你在模板中使用
{{ myComputed }},或在 JS 中读取myComputed.value时,会调用get()。 - 它应该返回一个值 ,这个值通常基于其他响应式数据(如
ref或reactive对象)计算而来。 - Vue 会自动追踪
get()内部访问的所有响应式依赖,并在它们变化时重新计算。
set(newValue) ------ 赋值时触发
- 当你执行
myComputed.value = someValue(例如通过v-model),就会调用set(newValue)。 newValue就是你试图赋给计算属性的值。- 在
set中,你不能直接修改计算属性本身 (因为它没有存储空间),而是要反向更新它所依赖的原始数据。
五、 Vue 开发的核心思维转变
| 传统开发(操作 DOM) | Vue 开发(操作数据) |
|---|---|
| 想加一条任务 → appendChild 一个 li | 想加一条任务 → todos.push(新对象) |
| 想改标题 → getElementById().innerText = xxx | 想改标题 → title.value = '新标题' |
| 想全选 → 遍历所有 checkbox 打钩 | 想全选 → allDone = true |
你看,Vue 让我们彻底从「操作页面元素」变成了「操作数据」,代码更清晰、更容易维护、也更好测试。
最后总结:
- ref 响应式数据(核心核心)
- v-model 双向绑定(表单必备)
- v-for / v-if 指令(模板必备)
- computed 计算属性(性能+优雅)
- Composition API 的 script setup(现代写法)