19 KiB
19 KiB
Vue 新手完全指南 - 基于你的待办事项项目
📚 目录
Vue 基础概念
🎯 什么是Vue?
Vue.js 是一个渐进式JavaScript框架,用于构建用户界面。你的项目使用的是Vue 3,这是目前最新的版本。
核心特点:
- 响应式:数据变化时,界面自动更新
- 组件化:将复杂界面拆分成小组件
- 声明式:描述"要什么结果",而不是"怎么做"
🏗️ Vue应用的工作原理
// 你的项目入口文件:src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
解释:
createApp()- 创建Vue应用实例App- 根组件(你的整个应用)mount('#app')- 挂载到HTML元素上
项目结构理解
📁 你的项目文件结构
todo-app/
├── src/
│ ├── App.vue # 主组件(你的核心代码)
│ ├── main.js # 应用入口
│ └── assets/ # 静态资源
├── public/ # 公共文件
├── package.json # 项目配置
└── vite.config.js # 构建工具配置
📄 单文件组件 (.vue文件)
你的App.vue是一个单文件组件,包含三个部分:
<template>
<!-- HTML模板:界面结构 -->
</template>
<script setup>
// JavaScript逻辑:数据和方法
</script>
<style scoped>
/* CSS样式:外观设计 */
</style>
新手要点:
<template>= 你看到的页面内容<script setup>= 页面的功能逻辑<style scoped>= 只影响当前组件的样式
Composition API 详解
🔥 什么是 <script setup>?
你的项目使用了Vue 3的Composition API,这是推荐的新写法:
<script setup>
import { ref, computed } from 'vue'
// 这里写你的代码逻辑
</script>
为什么用<script setup>?
- ✅ 代码更简洁
- ✅ 性能更好
- ✅ TypeScript支持更好
- ✅ 这是Vue 3推荐写法
📦 导入Vue功能
// 你的项目第2行
import { ref, computed } from 'vue'
解释:
ref- 创建响应式数据computed- 创建计算属性- 这些是Vue提供的功能函数
响应式数据系统
🎪 什么是响应式?
响应式 = 数据变化时,页面自动更新
📝 ref() - 基本响应式数据
// 你的项目代码示例
const newTodo = ref('') // 字符串
const todos = ref([...]) // 数组
const filter = ref('all') // 字符串
// 使用方式
console.log(newTodo.value) // 读取值
newTodo.value = '新的待办事项' // 修改值
新手重点:
- 📌
ref()创建响应式数据 - 📌 在JavaScript中用
.value访问 - 📌 在模板中直接用变量名(自动解包)
🔄 响应式数组操作
// 你的项目中的数组操作
const todos = ref([
{ id: 1, text: '学习Vue基础语法', completed: false },
{ id: 2, text: '理解响应式数据', completed: true }
])
// 添加项目(第32-38行)
todos.value.push({
id: Date.now(),
text: newTodo.value.trim(),
completed: false
})
// 删除项目(第42-46行)
const index = todos.value.findIndex(todo => todo.id === id)
todos.value.splice(index, 1)
// 过滤项目(第56行)
todos.value = todos.value.filter(todo => !todo.completed)
新手要点:
- 🎯 数组方法:
push(),splice(),filter(),find(),findIndex() - 🎯 修改数组后,页面自动更新
- 🎯
Date.now()生成唯一ID
🧮 computed() - 计算属性
// 你的项目计算属性示例
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter(todo => !todo.completed)
case 'completed':
return todos.value.filter(todo => todo.completed)
default:
return todos.value
}
})
const remainingCount = computed(() => {
return todos.value.filter(todo => !todo.completed).length
})
计算属性特点:
- ✨ 基于其他数据计算得出
- ✨ 依赖数据变化时自动重新计算
- ✨ 有缓存,性能更好
- ✨ 在模板中像普通数据一样使用
什么时候用计算属性?
- 📊 数据统计(如:未完成数量)
- 🔍 数据过滤(如:按条件筛选)
- 🔄 数据转换(如:格式化显示)
模板语法与指令
📝 插值表达式 {{}}
<!-- 你的项目中的插值示例 -->
<h1>Vue 待办事项</h1> <!-- 静态文本 -->
<span>还有 {{ remainingCount }} 项未完成</span> <!-- 动态数据 -->
<span>全部 ({{ todos.length }})</span> <!-- 表达式计算 -->
新手要点:
- 🔤
{{ }}用于显示数据 - 🔤 可以是变量、表达式、方法调用
- 🔤 自动转换为字符串显示
🎛️ v-model - 双向数据绑定
<!-- 你的项目第66行 -->
<input v-model="newTodo" placeholder="添加新的待办事项..." />
<!-- 你的项目第110行 -->
<input type="checkbox" v-model="todo.completed" />
双向绑定的含义:
- 📥 输入框内容变化 → 数据自动更新
- 📤 数据变化 → 输入框内容自动更新
常见用法:
<!-- 文本输入 -->
<input v-model="message" />
<!-- 复选框 -->
<input type="checkbox" v-model="checked" />
<!-- 单选框 -->
<input type="radio" value="A" v-model="picked" />
<!-- 选择框 -->
<select v-model="selected">
<option value="apple">苹果</option>
</select>
👁️ v-show 和 v-if - 条件显示
<!-- 你的项目中的条件显示 -->
<main v-show="todos.length"> <!-- 有数据时显示 -->
<button v-show="todos.length > remainingCount"> <!-- 有已完成项时显示 -->
<div v-show="!todos.length"> <!-- 无数据时显示 -->
v-show vs v-if 的区别:
v-show: 通过CSSdisplay控制显示/隐藏v-if: 真正的条件渲染,元素会被创建/销毁
什么时候用哪个?
- 频繁切换 → 用
v-show - 很少改变 → 用
v-if
🔄 v-for - 列表渲染
<!-- 你的项目第102-107行 -->
<li v-for="todo in filteredTodos" :key="todo.id" class="todo-item">
<div class="todo-content">
<input type="checkbox" v-model="todo.completed" />
<span class="todo-text">{{ todo.text }}</span>
</div>
<button @click="removeTodo(todo.id)">删除</button>
</li>
v-for 重点:
- 📋 遍历数组或对象
- 🔑
:key必须提供(性能优化) - 🔑 key应该是唯一值(如ID)
常见用法:
<!-- 遍历数组 -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 遍历对象 -->
<li v-for="(value, key) in object" :key="key">{{ key }}: {{ value }}</li>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
🎨 :class - 动态类绑定
<!-- 你的项目中的类绑定示例 -->
<button :class="{ active: filter === 'all' }">全部</button>
<li :class="{ completed: todo.completed }" class="todo-item">
类绑定语法:
<!-- 对象语法 -->
<div :class="{ active: isActive, disabled: isDisabled }"></div>
<!-- 数组语法 -->
<div :class="[activeClass, errorClass]"></div>
<!-- 混合使用 -->
<div class="static-class" :class="{ dynamic: isDynamic }"></div>
新手理解:
- 🎯
{ active: filter === 'all' }- 如果
filter === 'all'为真,添加active类 - 如果为假,不添加
- 如果
事件处理
🖱️ @click - 点击事件
<!-- 你的项目中的点击事件 -->
<button @click="addTodo">添加</button>
<button @click="filter = 'all'">全部</button>
<button @click="removeTodo(todo.id)">删除</button>
<button @click="clearCompleted">清除已完成</button>
事件处理方式:
<!-- 调用方法 -->
<button @click="handleClick">点击我</button>
<!-- 直接执行代码 -->
<button @click="count++">计数+1</button>
<!-- 传递参数 -->
<button @click="handleClick(item.id)">删除</button>
<!-- 传递事件对象 -->
<button @click="handleClick($event)">获取事件</button>
⌨️ @keyup - 键盘事件
<!-- 你的项目第67行 -->
<input @keyup.enter="addTodo" />
键盘事件修饰符:
<input @keyup.enter="submit"> <!-- 回车键 -->
<input @keyup.esc="cancel"> <!-- ESC键 -->
<input @keyup.space="toggle"> <!-- 空格键 -->
<input @keyup.tab="nextField"> <!-- Tab键 -->
<input @keyup.ctrl.enter="save"> <!-- Ctrl+Enter -->
📤 @change - 值变化事件
<!-- 你的项目第111行 -->
<input type="checkbox" @change="toggleTodo(todo.id)" />
常见事件类型:
@click- 点击@input- 输入(实时)@change- 值改变(失去焦点时)@submit- 表单提交@focus- 获得焦点@blur- 失去焦点
🛠️ 方法定义
// 你的项目中的方法定义
const addTodo = () => {
if (newTodo.value.trim()) { // 检查输入不为空
todos.value.push({ // 添加到数组
id: Date.now(), // 生成唯一ID
text: newTodo.value.trim(), // 去除前后空格
completed: false // 初始状态
})
newTodo.value = '' // 清空输入框
}
}
const removeTodo = (id) => {
const index = todos.value.findIndex(todo => todo.id === id)
if (index > -1) {
todos.value.splice(index, 1) // 从数组中删除
}
}
const toggleTodo = (id) => {
const todo = todos.value.find(todo => todo.id === id)
if (todo) {
todo.completed = !todo.completed // 切换完成状态
}
}
方法编写规范:
- ✅ 使用箭头函数
const method = () => {} - ✅ 参数验证(检查数据有效性)
- ✅ 防御性编程(检查对象是否存在)
- ✅ 一个方法做一件事
计算属性深入
🧩 计算属性 vs 方法的区别
计算属性(推荐):
// 你的项目使用的计算属性
const remainingCount = computed(() => {
return todos.value.filter(todo => !todo.completed).length
})
方法写法(不推荐):
const getRemainingCount = () => {
return todos.value.filter(todo => !todo.completed).length
}
为什么用计算属性?
- 🚀 有缓存 - 依赖不变时不重新计算
- 🚀 自动更新 - 依赖变化时自动重算
- 🚀 性能更好 - 避免重复计算
🔍 复杂计算属性示例
// 你的项目中的过滤计算属性
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter(todo => !todo.completed)
case 'completed':
return todos.value.filter(todo => todo.completed)
default:
return todos.value
}
})
这个计算属性做了什么?
- 监听
filter.value的变化 - 根据筛选条件返回不同的待办列表
- 当
filter或todos变化时自动重新计算
💡 编写计算属性的技巧
// ✅ 好的计算属性
const completedTodos = computed(() => {
return todos.value.filter(todo => todo.completed)
})
// ✅ 链式计算属性
const completedTodosCount = computed(() => {
return completedTodos.value.length
})
// ❌ 避免副作用
const badComputed = computed(() => {
// 不要在计算属性中修改数据
todos.value.push({...}) // 错误!
return someValue
})
CSS 与样式处理
🎨 Scoped CSS
<style scoped>
.todo-app {
max-width: 600px;
margin: 0 auto;
}
</style>
scoped 的作用:
- 🔒 样式只影响当前组件
- 🔒 不会污染全局样式
- 🔒 避免样式冲突
🎭 CSS类的条件应用
<!-- 你的项目中的条件样式 -->
<button :class="{ active: filter === 'all' }">
<li :class="{ completed: todo.completed }" class="todo-item">
对应的CSS:
.filters button.active {
background-color: #42b883;
color: white;
}
.todo-item.completed {
opacity: 0.6;
background-color: #f8f9fa;
}
.completed .todo-text {
text-decoration: line-through;
color: #95a5a6;
}
🌈 CSS变量和主题色
/* 你的项目使用的主题色 */
.new-todo:focus {
border-color: #42b883; /* Vue绿色 */
}
.add-btn {
background-color: #42b883;
}
.filters button.active {
background-color: #42b883;
}
📱 响应式设计
/* 你的项目中没有用到,但建议添加 */
@media (max-width: 768px) {
.todo-app {
padding: 10px;
}
.input-container {
flex-direction: column;
}
}
组件开发模式
🧱 单文件组件的优势
你的项目使用单文件组件(.vue文件):
<template>
<!-- 模板 -->
</template>
<script setup>
<!-- 逻辑 -->
</script>
<style scoped>
/* 样式 */
</style>
优势:
- 📦 高内聚 - 相关代码在一起
- 🔧 易维护 - 修改功能只需改一个文件
- 🚀 易复用 - 整个组件可以在其他地方使用
🔄 组件拆分建议
你的项目目前是单组件,可以考虑拆分:
// 可以拆分成的组件
components/
├── TodoApp.vue // 主容器组件
├── TodoInput.vue // 输入框组件
├── TodoList.vue // 列表组件
├── TodoItem.vue // 单个待办项组件
├── TodoFilter.vue // 过滤器组件
└── TodoFooter.vue // 底部统计组件
组件拆分原则:
- 🎯 单一职责 - 一个组件做一件事
- 🎯 合理大小 - 不要太大也不要太小
- 🎯 易于理解 - 功能清晰明确
📡 组件通信(进阶)
当你拆分组件后,需要组件间通信:
// 父组件传数据给子组件 (Props)
<TodoItem :todo="todo" @toggle="toggleTodo" />
// 子组件触发父组件事件 (Emit)
const emit = defineEmits(['toggle'])
emit('toggle', todo.id)
构建与部署
⚡ Vite 构建工具
你的项目使用 Vite 作为构建工具:
// vite.config.js
export default defineConfig({
plugins: [
vue(), // Vue支持
vueDevTools(), // 开发工具
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})
Vite的优势:
- ⚡ 启动速度快
- ⚡ 热更新快
- ⚡ 构建速度快
- ⚡ 支持现代JavaScript特性
🚀 开发和构建命令
# 开发模式(你正在使用的)
npm run dev
# 构建生产版本
npm run build
# 预览构建结果
npm run preview
📦 依赖管理
// package.json 中的依赖
{
"dependencies": {
"vue": "^3.5.13" // Vue 3框架
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3", // Vue插件
"vite": "^6.2.4", // 构建工具
"vite-plugin-vue-devtools": "^7.7.2" // 开发工具
}
}
🎓 学习路径和下一步
📚 基于你项目的学习顺序
第1阶段:巩固基础(你已经在用)
- ✅ 响应式数据 (
ref) - ✅ 计算属性 (
computed) - ✅ 事件处理 (
@click,@keyup) - ✅ 条件渲染 (
v-show) - ✅ 列表渲染 (
v-for) - ✅ 双向绑定 (
v-model)
第2阶段:进阶功能
- 🔄 组件拆分和复用
- 🔄 组件通信 (Props & Emit)
- 🔄 生命周期钩子 (
onMounted,onUpdated) - 🔄 侦听器 (
watch,watchEffect)
第3阶段:实用技能
- 🚀 路由管理 (Vue Router)
- 🚀 状态管理 (Pinia)
- 🚀 HTTP请求 (Axios)
- 🚀 UI组件库 (Element Plus, Ant Design Vue)
💪 练习建议
基于你的项目扩展:
- 添加编辑功能 - 双击编辑待办事项
- 添加优先级 - 给待办事项分优先级
- 添加分类 - 工作、生活、学习分类
- 添加截止日期 - 为待办事项设置期限
- 数据持久化 - 使用 localStorage 保存数据
🐛 常见新手错误和解决方案
1. 忘记 .value
// ❌ 错误
const count = ref(0)
console.log(count) // 输出: RefImpl对象
count++ // 不会工作
// ✅ 正确
console.log(count.value) // 输出: 0
count.value++ // 正确的修改方式
2. 直接修改 props
// ❌ 错误 - 不要直接修改父组件传来的数据
props.todo.text = 'new text'
// ✅ 正确 - 通过事件通知父组件
emit('update-todo', { id: props.todo.id, text: 'new text' })
3. 缺少 key 属性
<!-- ❌ 错误 -->
<li v-for="item in items">{{ item.name }}</li>
<!-- ✅ 正确 -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
4. 在计算属性中修改数据
// ❌ 错误
const processedData = computed(() => {
originalData.value.push(newItem) // 不要在计算属性中修改数据
return originalData.value
})
// ✅ 正确
const processedData = computed(() => {
return originalData.value.map(item => ({ ...item, processed: true }))
})
🛠️ 调试技巧
1. 使用 Vue DevTools 在浏览器中安装 Vue DevTools 扩展,可以:
- 查看组件树
- 检查响应式数据
- 追踪事件
2. 控制台调试
// 在方法中添加调试输出
const addTodo = () => {
console.log('添加前的todos:', todos.value)
console.log('输入的内容:', newTodo.value)
if (newTodo.value.trim()) {
// ... 你的逻辑
}
console.log('添加后的todos:', todos.value)
}
3. 模板调试
<!-- 在模板中显示数据进行调试 -->
<div>{{ todos }}</div>
<div>当前筛选: {{ filter }}</div>
<div>计算属性结果: {{ filteredTodos }}</div>
📖 推荐学习资源
官方文档:
实用教程:
- Vue 3 快速上手
- Composition API 深入理解
- Vue 生态系统指南
练习项目建议:
- 完善当前的待办事项应用
- 制作一个简单的计算器
- 开发一个天气查询应用
- 创建一个个人博客系统
🎉 总结
恭喜你!你的待办事项项目已经使用了Vue 3的核心特性:
✅ 你已经掌握的:
- Composition API (
<script setup>) - 响应式数据 (
ref) - 计算属性 (
computed) - 事件处理 (
@click,@keyup) - 模板语法 (
v-model,v-for,v-show) - 动态类绑定 (
:class)
🚀 你的代码质量很高:
- 使用了现代的Vue 3语法
- 数据流清晰合理
- 用户体验良好
- 代码结构规范
📈 继续提升的方向:
- 组件化开发
- 更复杂的状态管理
- 与后端API交互
- 更丰富的用户界面
继续保持学习的热情,Vue.js的世界还有很多精彩等你探索!🌟