Vue3 作为 Vue 框架的重大升级版本,于2020年正式发布,相比 Vue2 带来了性能大幅提升 、组合式API(Composition API) 、更好的TypeScript支持 、Tree-Shaking优化等核心特性,彻底解决了 Vue2 中大型项目代码复用困难、选项式API(Options API)逻辑分散的痛点,同时保留了Vue的易用性,成为当前前端开发的主流框架之一。本文将从环境搭建、核心概念、实战案例到项目规范,全方位讲解Vue3基础,帮助新手快速入门并落地开发。
一、Vue3 核心优势(为什么选择Vue3?)
在开始学习前,先明确Vue3的核心升级点,理解其设计初衷,能让后续学习更有方向:
- 性能优化:重写了虚拟DOM,渲染速度提升约50%;支持Tree-Shaking,打包体积更小(按需引入API,未使用的代码会被剔除);
- 组合式API(Composition API) :替代Vue2的选项式API,将分散在
data、methods、watch中的同一业务逻辑聚合在一起,更易维护和复用,尤其适合大型项目; - 更好的TS支持:Vue3源码由TypeScript编写,天生对TypeScript友好,类型推导更完善,开发时能获得更优的代码提示;
- 新特性加持 :新增
Teleport(组件瞬移)、Suspense(异步组件加载)、v-model重构(支持多值绑定)、全局API重构(更易树摇)等; - 向下兼容:大部分Vue2的语法在Vue3中仍可使用,支持渐进式迁移,老项目可逐步升级。
二、环境搭建:Vue3 项目创建与运行
Vue3 官方推荐使用 Vite 作为构建工具(替代Vue2的Vue-CLI),Vite基于ESModule实现按需编译,启动速度、热更新速度远超Webpack,是当前Vue3项目的标配。以下是完整的环境搭建步骤,包含前置依赖和项目创建。
2.1 前置依赖:Node.js 安装
Vue3 项目依赖Node.js(提供npm包管理工具),要求Node.js版本 ≥ 14.18.0 或 ≥ 16.0.0。
-
下载地址:Node.js 官方网站(选择LTS长期支持版,兼容性更好)
-
验证安装:安装完成后,打开终端/命令行,输入以下命令,显示版本号即安装成功:
bashnode -v # 查看node版本 npm -v # 查看npm版本
2.2 创建Vue3 项目(Vite 方式)
使用Vite的官方创建命令,一步生成Vue3项目,支持选择Vue2/Vue3、TypeScript/JavaScript,操作简单:
-
执行创建命令(项目名可自定义,建议小写+横杠,如
vue3-demo):bashnpm create vite@latest 你的项目名 -- --template vue-
如需创建Vue3 + TypeScript 项目,将模板改为
vue-ts:bashnpm create vite@latest 你的项目名 -- --template vue-ts
-
-
进入项目目录,安装依赖:
bashcd 你的项目名 # 进入项目根目录 npm install # 安装项目依赖(生成node_modules文件夹) -
启动开发服务器:
bashnpm run dev -
访问项目:终端会显示本地访问地址(通常是
http://127.0.0.1:5173/),打开浏览器访问即可看到Vue3默认页面。
2.3 项目目录结构解析(核心目录说明)
创建完成后,项目核心目录结构如下(剔除了无关配置文件,聚焦开发核心):
vue3-demo/
├── node_modules/ # 项目依赖包(npm install生成)
├── public/ # 静态资源目录(不会被Vite编译,如图片、favicon)
├── src/ # 开发核心目录(所有业务代码都写在这里)
│ ├── assets/ # 静态资源(会被Vite编译,如css、图片、字体)
│ ├── components/ # 公共组件目录(可复用的组件,如按钮、卡片)
│ ├── App.vue # 根组件(所有页面/组件的入口)
│ └── main.js # 项目入口文件(创建Vue实例,挂载根组件)
├── package.json # 项目配置文件(依赖、脚本命令如npm run dev)
└── index.html # Vite入口html(唯一的html文件,单页应用核心)
核心文件说明:
main.js:Vue3的入口文件,负责创建应用实例、挂载根组件,是项目的启动入口;App.vue:根组件,所有自定义组件、页面都会被引入到这个组件中,相当于项目的"根容器";package.json:管理项目依赖和脚本命令,比如npm run dev对应启动开发服务器,npm run build对应打包生产环境代码。
三、Vue3 核心基础:从入口到组件
3.1 项目入口文件:main.js 详解
Vue3 重构了全局API,将Vue2的new Vue()改为**createApp**创建应用实例,更符合模块化设计,且支持多实例隔离。以下是Vue3原生main.js的核心代码及解析(无任何第三方插件):
javascript
// 1. 从vue中导入createApp方法(Vue3的核心API,创建应用实例)
import { createApp } from 'vue'
// 2. 导入根组件App.vue(项目所有组件的入口)
import App from './App.vue'
// 3. 导入全局样式(可选,如assets/css/global.css)
import './assets/main.css'
// 4. 创建应用实例 + 挂载根组件到DOM节点(#app)
// createApp(App):基于根组件创建Vue应用实例
// .mount('#app'):将应用实例挂载到index.html中id为app的DOM节点上
createApp(App).mount('#app')
关键变化(对比Vue2):
- Vue2:
new Vue({ el: '#app', render: h => h(App) }),通过构造函数创建实例; - Vue3:
createApp(App).mount('#app'),通过函数式API创建实例,更轻量、无全局污染; - 核心优势:
createApp支持链式调用,且多个实例相互独立(如const app1 = createApp(App1); const app2 = createApp(App2)),适合在一个页面中嵌入多个Vue应用。
3.2 Vue 单文件组件(SFC):核心组成
Vue的核心特性之一是单文件组件(Single File Component,简称SFC) ,即.vue文件,一个文件包含**模板(template)、脚本(script)、样式(style)**三部分,实现了组件的"代码封装",让结构、逻辑、样式高度内聚。
所有.vue组件都遵循统一的核心结构,缺一不可(样式可选),基础模板如下:
vue
<template>
<!-- 模板部分:组件的HTML结构,负责页面渲染 -->
<!-- 支持Vue的指令(v-bind、v-for、v-if等),根节点可多个(Vue3新增,Vue2要求单根节点) -->
<div class="component-container">
<h1>{{ msg }}</h1>
<button @click="handleClick">点击按钮</button>
</div>
</template>
<script setup>
<!-- 脚本部分:组件的逻辑代码(数据、方法、生命周期等) -->
<!-- setup是Vue3的语法糖,组合式API的核心,后续详细讲解 -->
// 响应式数据
const msg = 'Hello Vue3!'
// 事件方法
const handleClick = () => {
alert('你点击了按钮!')
}
</script>
<style scoped>
<!-- 样式部分:组件的CSS样式 -->
<!-- scoped属性:样式私有化,仅作用于当前组件(避免样式污染) -->
.component-container {
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #42b983; /* Vue官方主色 */
}
</style>
三部分详细说明:
-
template 模板
- 负责组件的DOM结构渲染,支持所有Vue内置指令(
v-bind、v-on、v-for、v-if等); - Vue3 取消了"单根节点限制",模板中可以直接写多个同级根节点(如
<h1></h1><p></p>),无需用<div>包裹,解决了Vue2的嵌套冗余问题; - 插值表达式
{``{ 变量名 }}:用于渲染脚本部分的响应式数据,支持简单的表达式(如{``{ num + 1 }}、{``{ msg.toUpperCase() }})。
- 负责组件的DOM结构渲染,支持所有Vue内置指令(
-
script 脚本
- 负责组件的核心逻辑:定义数据、方法、生命周期钩子、引入依赖、注册组件等;
setup是Vue3的语法糖 (后续重点讲解),是组合式API的入口,写在<script setup>中的代码会在组件创建时自动执行,无需额外配置;- 脚本部分的变量、方法,可直接在模板中使用(如上述的
msg、handleClick),无需像Vue2那样通过this调用。
-
style 样式
- 负责组件的样式定义,支持原生CSS、SCSS/LESS(需安装对应依赖,如
npm install sass); scoped属性:样式私有化核心,添加后,当前组件的样式仅作用于自身,不会影响父组件、子组件或其他组件,解决了大型项目的样式污染问题;- 如需让样式作用于子组件,可使用Vue的深度选择器
::v-deep(如::v-deep .child-class { color: red; })。
- 负责组件的样式定义,支持原生CSS、SCSS/LESS(需安装对应依赖,如
四、Vue3 核心语法:组合式API(Composition API)
组合式API是Vue3的核心灵魂 ,也是与Vue2选项式API最核心的区别。它解决了Vue2中"同一业务逻辑的代码分散在data、methods、watch、computed中"的问题,让相关逻辑聚合在一起,更易维护和复用。
4.1 为什么放弃Vue2的选项式API?
Vue2的选项式API(Options API)采用"选项分离"的方式组织代码,比如:
javascript
// Vue2 选项式API
export default {
data() {
return { count: 0 } // 数据写在data
},
methods: {
addCount() { this.count++ } // 方法写在methods
},
watch: {
count(newVal) { console.log('count变化了:', newVal) } // 监听写在watch
}
}
这种方式在小型项目中很易用,但在大型项目中,一个业务逻辑可能涉及数据、方法、监听、计算属性等,需要在多个选项之间来回切换,代码可读性和可维护性大幅下降,且代码复用只能通过mixins(混入),存在命名冲突、逻辑来源不清晰的问题。
而Vue3的组合式API,将同一业务逻辑的所有代码聚合在一起,即使项目再大,也能快速定位逻辑,且支持组合式函数实现代码复用,无命名冲突问题。
4.2 组合式API 入口:setup 语法糖
<script setup> 是Vue3推荐的组合式API写法,也是最简洁、最高效 的写法(替代了原始的setup函数),具有以下核心特性:
- 自动执行 :写在
<script setup>中的代码,会在组件创建之前 (beforeCreate生命周期前)自动执行,无需手动调用; - 无需导出 :脚本中的变量、方法、组件等,无需通过
export default导出,可直接在模板中使用; - 简化注册 :引入的组件无需手动注册(如
import Button from './components/Button.vue',直接在模板中写<Button/>即可); - 天生支持TS:与TypeScript无缝集成,类型推导更完善,开发体验更好。
注意 :<script setup> 是Vue3.2及以上版本的特性,当前Vite创建的Vue3项目默认都是3.2+,可放心使用。
4.3 响应式数据:ref 和 reactive(核心)
Vue的核心特性是数据驱动视图 :当数据发生变化时,视图会自动更新。而实现这一特性的基础,是响应式数据 。Vue3 提供了两个核心API创建响应式数据:ref 和 reactive,适用于不同场景。
4.3.1 ref:用于基本类型数据(字符串、数字、布尔值)
ref 是Vue3中最常用的响应式API,专门用于包装基本类型数据,使其成为响应式数据,同时也支持包装对象类型数据(推荐用于基本类型)。
使用步骤:
- 从
vue中导入ref; - 使用
ref(初始值)创建响应式数据; - 模板中直接使用变量名,脚本中通过
变量名.value访问/修改值。
示例代码:
vue
<template>
<div>
<!-- 模板中直接使用,无需.value -->
<p>当前计数:{{ count }}</p>
<button @click="addCount">点击+1</button>
<p>用户名:{{ name }}</p>
</div>
</template>
<script setup>
// 1. 导入ref
import { ref } from 'vue'
// 2. 创建响应式数据(基本类型)
const count = ref(0) // 数字
const name = ref('Vue3入门') // 字符串
const isShow = ref(true) // 布尔值
// 3. 脚本中修改响应式数据,需通过.value
const addCount = () => {
count.value++ // 必须写.value,否则数据不更新,视图也不刷新
if (count.value >= 5) {
isShow.value = false
}
}
</script>
4.3.2 reactive:用于对象/数组类型数据
reactive 专门用于包装对象或数组类型数据 ,使其成为响应式数据,返回一个响应式的代理对象(Proxy),直接操作对象属性即可实现数据响应式,无需.value。
使用步骤:
- 从
vue中导入reactive; - 使用
reactive({ ... })或reactive([ ... ])创建响应式数据; - 模板和脚本中直接访问/修改对象属性/数组元素。
示例代码:
vue
<template>
<div>
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<button @click="updateAge">年龄+1</button>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
<button @click="addItem">添加列表项</button>
</div>
</template>
<script setup>
// 1. 导入reactive
import { reactive } from 'vue'
// 2. 创建响应式对象
const user = reactive({
name: '张三',
age: 20
})
// 2. 创建响应式数组
const list = reactive(['Vue3', 'Vite', 'Composition API'])
// 3. 直接修改属性/元素,无需.value
const updateAge = () => {
user.age++
}
const addItem = () => {
list.push('ref & reactive')
}
</script>
4.3.3 ref vs reactive:核心区别与使用场景
| 特性 | ref | reactive |
|---|---|---|
| 适用类型 | 基本类型(推荐)、对象/数组 | 对象/数组(仅支持) |
| 访问方式 | 脚本中需.value,模板中无需 |
模板/脚本中均直接访问属性/元素 |
| 返回值 | 包含value的响应式对象 |
响应式代理对象(Proxy) |
| 解构响应性 | 直接解构会丢失响应性(需toRefs) | 直接解构会丢失响应性(需toRefs) |
核心使用原则:
- 基本类型数据(string/number/boolean):优先使用
ref; - 对象/数组类型数据:优先使用
reactive; - 简单场景用
ref,复杂业务对象用reactive,两者可结合使用(如reactive对象中包含ref数据)。
4.4 计算属性:computed
计算属性用于基于现有响应式数据,生成新的派生数据 ,具有缓存特性:只有当依赖的响应式数据发生变化时,计算属性才会重新计算;如果依赖数据未变化,直接返回缓存的结果,相比普通方法更高效。
使用场景:数据格式化、多数据组合计算、条件判断派生数据等(如:根据性别值显示"男/女"、根据商品数量和单价计算总价)。
使用步骤:
- 从
vue中导入computed; - 调用
computed(() => { 计算逻辑,返回结果 }); - 模板中直接使用,无需加括号。
示例代码:
vue
<template>
<div>
<p>原始价格:{{ price }} 元</p>
<p>折扣后价格:{{ discountPrice }} 元</p> <!-- 直接使用 -->
<p>商品状态:{{ goodsStatus }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 原始响应式数据
const price = ref(100)
const stock = ref(5) // 库存
// 计算属性:折扣后价格(依赖price)
const discountPrice = computed(() => {
return (price.value * 0.8).toFixed(2) // 8折,保留2位小数
})
// 计算属性:商品状态(依赖stock)
const goodsStatus = computed(() => {
return stock.value > 0 ? '有货' : '无货'
})
</script>
4.5 监听器:watch
监听器用于监听响应式数据的变化 ,并在数据变化时执行自定义逻辑(如:数据变化后发起请求、更新其他数据、做日志记录等)。Vue3的watch支持监听单个数据、多个数据、对象属性,功能比Vue2更强大。
4.5.1 监听单个ref数据
vue
<template>
<p>计数:{{ count }}</p>
<button @click="count++">+1</button>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
// 监听单个ref数据:watch(监听的变量, 回调函数(newVal, oldVal))
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`)
// 数据变化后的逻辑,如发起请求
})
</script>
4.5.2 监听多个数据
将多个监听的变量放入数组中,回调函数的参数是一个数组,对应新值和旧值。
vue
<script setup>
import { ref, watch } from 'vue'
const name = ref('张三')
const age = ref(20)
// 监听多个数据
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
console.log(`姓名:${oldName}→${newName},年龄:${oldAge}→${newAge}`)
})
</script>
4.5.3 监听reactive对象/对象属性
- 监听整个
reactive对象:直接传入对象,回调函数会在对象任意属性变化时触发; - 监听
reactive对象的单个属性:需传入函数,返回要监听的属性(避免监听整个对象)。
vue
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: '张三',
age: 20,
address: {
city: '北京'
}
})
// 1. 监听整个reactive对象(任意属性变化都会触发)
watch(user, (newVal, oldVal) => {
console.log('user对象变化了:', newVal)
})
// 2. 监听reactive对象的单个属性(推荐,性能更好)
watch(() => user.age, (newAge, oldAge) => {
console.log(`年龄变化:${oldAge}→${newAge}`)
})
// 3. 监听嵌套属性
watch(() => user.address.city, (newCity) => {
console.log('城市变化:', newCity)
})
// 触发监听
const updateUser = () => {
user.age = 21 // 触发2、1
user.address.city = '上海' // 触发3、1
}
</script>
4.6 Vue3 生命周期钩子
生命周期钩子用于在组件的不同阶段执行自定义逻辑 (如:组件创建时初始化数据、挂载后操作DOM、销毁前清除定时器)。Vue3的组合式API中,生命周期钩子以函数形式 提供,需从vue中导入后使用,相比Vue2更贴合模块化设计。
4.6.1 Vue3 生命周期钩子列表(与Vue2对应)
Vue3 保留了Vue2的核心生命周期,仅修改了两个钩子的名称,新增了setup(替代beforeCreate和created),以下是组合式API中的生命周期钩子与Vue2的对应关系:
| Vue3 组合式API钩子 | 对应Vue2选项式API | 执行时机 | 核心用途 |
|---|---|---|---|
| setup | beforeCreate/created | 组件创建之前 | 初始化数据、创建响应式数据 |
| onBeforeMount | beforeMount | 组件挂载到DOM之前 | 准备挂载相关逻辑 |
| onMounted | mounted | 组件挂载到DOM之后 | 操作DOM、发起初始请求 |
| onBeforeUpdate | beforeUpdate | 数据变化,视图更新之前 | 准备更新视图的逻辑 |
| onUpdated | updated | 数据变化,视图更新之后 | 视图更新后操作DOM |
| onBeforeUnmount | beforeDestroy | 组件销毁之前 | 清除定时器、取消事件监听 |
| onUnmounted | destroyed | 组件销毁之后 | 清理遗留资源 |
| onErrorCaptured | errorCaptured | 捕获子组件的错误时 | 处理子组件错误,避免程序崩溃 |
4.6.2 生命周期钩子使用示例
组合式API中,生命周期钩子只能在<script setup>中调用,且会在对应阶段自动执行,示例代码:
vue
<template>
<p>{{ count }}</p>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, onUpdated } from 'vue'
const count = ref(0)
let timer = null
// 组件挂载后执行:开启定时器
onMounted(() => {
console.log('组件挂载完成,可操作DOM')
// 开启定时器,每秒count+1
timer = setInterval(() => {
count.value++
}, 1000)
})
// 视图更新后执行
onUpdated(() => {
console.log('视图更新完成,当前count:', count.value)
})
// 组件销毁前执行:清除定时器(避免内存泄漏)
onBeforeUnmount(() => {
console.log('组件即将销毁,清理资源')
clearInterval(timer) // 清除定时器
})
</script>
核心注意点:
setup中无需使用onBeforeCreate和created,因为setup的执行时机正好覆盖这两个阶段,所有初始化逻辑直接写在setup中即可;- 组件销毁前,必须清理定时器、事件监听、网络请求等资源,避免内存泄漏,这是前端开发的重要规范;
- 生命周期钩子可以多次调用,执行顺序为调用顺序(如多次调用
onMounted,会按顺序依次执行)。
五、Vue3 组件通信:父子组件数据传递
组件是Vue开发的核心,实际项目中会将页面拆分为多个组件(如头部组件、导航组件、内容组件),组件之间的数据传递(通信)是必备技能。Vue3中组件通信的核心方式与Vue2一致,但写法因组合式API略有简化,以下讲解最常用的父子组件通信(占实际开发的80%以上)。
5.1 父传子:props 传递数据
核心逻辑 :父组件通过自定义属性 向子组件传递数据,子组件通过defineProps接收并使用,props是单向数据流(子组件不能直接修改父组件传递的props,避免数据混乱)。
步骤1:创建子组件(如src/components/Child.vue)
使用defineProps接收父组件传递的数据,defineProps是Vue3的内置宏(无需导入),直接在<script setup>中使用。
vue
<!-- Child.vue 子组件 -->
<template>
<div class="child">
<h3>子组件</h3>
<p>父组件传递的名称:{{ name }}</p>
<p>父组件传递的年龄:{{ age }}</p>
</div>
</template>
<script setup>
// 接收父组件的props,支持指定类型(基础类型校验)
const props = defineProps({
name: String, // 名称:字符串类型
age: Number // 年龄:数字类型
})
// 脚本中可通过props.xxx访问,模板中直接使用xxx
console.log('父组件传递的name:', props.name)
</script>
<style scoped>
.child {
padding: 20px;
border: 1px solid #42b983;
margin-top: 20px;
}
</style>
步骤2:父组件引入并传递数据(如App.vue)
父组件通过自定义属性 (如name、age)将响应式数据传递给子组件,属性值绑定响应式数据(用v-bind:或简写:)。
vue
<!-- App.vue 父组件 -->
<template>
<div class="parent">
<h3>父组件</h3>
<button @click="changeAge">修改年龄</button>
<!-- 引入子组件,通过自定义属性传递数据(v-bind绑定响应式数据) -->
<Child :name="userName" :age="userAge" />
</div>
</template>
<script setup>
// 1. 导入子组件(无需手动注册,直接使用)
import Child from './components/Child.vue'
// 2. 定义父组件的响应式数据
import { ref } from 'vue'
const userName = ref('张三')
const userAge = ref(20)
// 3. 修改父组件数据,子组件会自动更新(props单向数据流)
const changeAge = () => {
userAge.value++
}
</script>
<style scoped>
.parent {
padding: 20px;
border: 1px solid #666;
}
</style>
5.2 子传父:emit 触发事件传递数据
核心逻辑 :子组件通过defineEmits定义自定义事件,通过emit触发事件并传递数据;父组件通过自定义事件监听 (@事件名)接收子组件传递的数据,并在回调函数中处理。
步骤1:子组件定义并触发自定义事件(修改Child.vue)
- 使用
defineEmits定义自定义事件(内置宏,无需导入); - 调用
emit方法触发事件,第二个参数为要传递给父组件的数据。
vue
<!-- Child.vue 子组件 -->
<template>
<div class="child">
<h3>子组件</h3>
<p>子组件输入:<input v-model="inputVal" /></p>
<!-- 点击按钮,触发自定义事件,传递数据 -->
<button @click="sendToParent">将数据传递给父组件</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 1. 定义自定义事件(如:send-data)
const emit = defineEmits(['send-data'])
// 子组件的响应式数据
const inputVal = ref('')
// 2. 触发自定义事件,传递数据(emit('事件名', 数据1, 数据2,...))
const sendToParent = () => {
if (inputVal.value) {
emit('send-data', inputVal.value) // 触发send-data事件,传递输入值
inputVal.value = '' // 清空输入框
}
}
</script>
步骤2:父组件监听自定义事件(修改App.vue)
父组件通过@自定义事件名监听子组件的事件,在回调函数中接收子组件传递的数据并处理。
vue
<!-- App.vue 父组件 -->
<template>
<div class="parent">
<h3>父组件</h3>
<p>子组件传递的数据:{{ childData }}</p>
<!-- 监听子组件的send-data事件,回调函数接收数据 -->
<Child @send-data="handleChildData" />
</div>
</template>
<script setup>
import Child from './components/Child.vue'
import { ref } from 'vue'
// 接收子组件数据的响应式变量
const childData = ref('')
// 监听子组件事件的回调函数,参数为子组件传递的数据
const handleChildData = (data) => {
childData.value = data
console.log('接收到子组件的数据:', data)
}
</script>
父子组件通信核心原则:
- 父传子:props 单向数据流,子组件只读不修改;
- 子传父:emit 触发事件,父组件监听处理;
- 如需子组件修改父组件数据,通过"子组件emit事件,父组件在回调中修改自身数据"实现,间接完成数据更新。
六、Vue3 实战小案例:待办事项(TodoList)
为了将以上基础知识点融会贯通,接下来实现一个简易的待办事项(TodoList) 案例,涵盖:响应式数据、事件处理、v-for/v-bind指令、组件通信(可选)、computed计算属性,是Vue3入门的经典实战案例。
6.1 案例需求
- 输入待办内容,点击按钮添加到待办列表;
- 显示所有待办事项,包含"完成/未完成"状态;
- 点击待办事项,切换完成/未完成状态;
- 计算并显示"未完成待办数量";
- 支持删除单个待办事项。
6.2 完整代码实现(App.vue)
vue
<template>
<div class="todo-list" style="width: 400px; margin: 50px auto;">
<h2 style="color: #42b983; text-align: center;">Vue3 TodoList</h2>
<!-- 输入框 + 添加按钮 -->
<div class="todo-input" style="display: flex; gap: 10px; margin-bottom: 20px;">
<input
v-model="inputVal"
type="text"
placeholder="请输入待办事项"
style="flex: 1; padding: 8px; border: 1px solid #eee; border-radius: 4px;"
@keyup.enter="addTodo" // 回车也能添加
/>
<button
@click="addTodo"
style="padding: 8px 16px; background: #42b983; color: #fff; border: none; border-radius: 4px; cursor: pointer;"
>
添加
</button>
</div>
<!-- 待办列表 -->
<ul style="list-style: none; padding: 0; margin: 0;">
<li
v-for="(todo, index) in todoList"
:key="index"
style="display: flex; justify-content: space-between; align-items: center; padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;"
:style="{ textDecoration: todo.done ? 'line-through' : 'none', color: todo.done ? '#999' : '#333' }"
>
<!-- 点击切换完成状态 -->
<span @click="toggleTodo(index)">{{ todo.text }}</span>
<!-- 删除按钮 -->
<button
@click="deleteTodo(index)"
style="color: #f56c6c; background: transparent; border: none; cursor: pointer;"
>
删除
</button>
</li>
</ul>
<!-- 未完成数量(计算属性) -->
<p style="text-align: center; margin-top: 20px; color: #666;">
未完成待办:{{ unDoneCount }} / 总数量:{{ todoList.length }}
</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 1. 响应式数据:输入框值
const inputVal = ref('')
// 2. 响应式数据:待办列表(数组+对象,用reactive/ref均可,这里用ref更简洁)
const todoList = ref([
{ text: '学习Vue3组合式API', done: false },
{ text: '实现TodoList案例', done: false }
])
// 3. 计算属性:未完成待办数量
const unDoneCount = computed(() => {
return todoList.value.filter(todo => !todo.done).length
})
// 4. 方法:添加待办
const addTodo = () => {
// 去除首尾空格,判空
const val = inputVal.value.trim()
if (!val) {
alert('请输入待办内容!')
return
}
// 添加到列表
todoList.value.push({ text: val, done: false })
// 清空输入框
inputVal.value = ''
}
// 5. 方法:切换待办完成状态
const toggleTodo = (index) => {
todoList.value[index].done = !todoList.value[index].done
}
// 6. 方法:删除待办
const deleteTodo = (index) => {
todoList.value.splice(index, 1)
}
</script>
6.3 案例运行效果
- 页面显示默认的2条待办事项,输入框可输入新内容,点击"添加"或回车即可新增;
- 点击待办事项文字,会切换划线(完成)/无划线(未完成)状态;
- 点击"删除"可移除对应待办事项;
- 底部实时显示未完成待办数量和总数量,数据自动更新。
该案例涵盖了Vue3入门的所有核心知识点,是检验基础是否掌握的最佳方式,建议手动敲写代码,理解每一行的逻辑。
七、Vue3 开发规范与最佳实践
为了让代码更易维护、团队协作更高效,结合Vue3的特性,整理了以下入门级开发规范,新手严格遵循,能养成良好的编码习惯:
7.1 组件相关规范
- 组件名:小写+横杠 (如
todo-item.vue、user-card.vue),避免大驼峰或纯大写,符合HTML自定义标签规范; - 组件文件:一个组件对应一个
.vue文件,公共组件放在src/components/,页面组件放在src/views/(大型项目); - 组件嵌套:避免超过3层嵌套(如父→子→孙→曾孙),多层嵌套会导致组件通信复杂,可使用Pinia(状态管理)优化;
- 样式规范:所有组件样式都添加
scoped属性,避免样式污染;如需修改子组件样式,使用::v-deep深度选择器,且尽量少用。
7.2 代码编写规范
- 响应式数据:基本类型用
ref,对象/数组用reactive,避免混用导致的响应性丢失; - 变量/方法命名:小驼峰 (如
userName、addTodo),语义化命名(避免a、b、fn1等无意义命名); - 模板指令:
v-bind简写为:,v-on简写为@,v-for必须配合:key(优先使用唯一ID,而非索引); - 生命周期:组件销毁前必须清理定时器、事件监听、网络请求等资源,避免内存泄漏;
- props规范:子组件接收props时,尽量指定类型校验 (如
String、Number),提高代码健壮性,大型项目可添加默认值和必传校验。
7.3 性能优化规范
- 避免频繁修改响应式数据:多次修改响应式数据时,尽量合并操作,减少视图更新次数;
- 计算属性替代频繁调用的方法:对于需要重复计算的逻辑,使用
computed(利用缓存特性),而非普通方法; v-for与v-if不混用:如需条件筛选列表,先通过computed过滤数据,再用v-for渲染,避免每次渲染都执行条件判断;- 按需引入:使用Vue3的Tree-Shaking特性,只导入需要的API(如
import { ref, computed } from 'vue'),而非导入整个Vue。
八、总结与后续学习方向
8.1 本文核心知识点总结
- 环境搭建:Vue3推荐使用Vite创建项目,前置依赖Node.js ≥14.18.0,核心命令
npm create vite@latest; - 核心文件:
main.js用createApp创建应用实例,.vue单文件组件包含template/script/style三部分; - 组合式API:
<script setup>是核心入口,响应式数据用ref(基本类型)和reactive(对象/数组); - 核心语法:
computed(计算属性,缓存)、watch(监听器,数据变化触发)、生命周期钩子(函数式,如onMounted); - 组件通信:父子组件核心方式为"父传子props,子传父emit",props是单向数据流;
- 实战案例:TodoList涵盖了Vue3入门的所有核心知识点,是基础落地的关键。
8.2 后续学习方向(从入门到进阶)
- 状态管理 :学习Vue3官方推荐的状态管理工具Pinia(替代Vuex),解决跨组件、跨页面的数据共享问题;
- 路由管理 :学习Vue Router 4(Vue3配套路由),实现单页应用的页面跳转、路由传参、路由守卫;
- 高级特性 :学习
Teleport(组件瞬移)、Suspense(异步组件)、provide/inject(跨层级组件通信)、自定义指令; - TypeScript集成:深入学习Vue3 + TypeScript开发,实现强类型约束,提高大型项目的可维护性;
- 工程化优化:学习Vite配置(如代理、环境变量)、ESLint/Prettier代码规范、Vue3性能优化(如虚拟滚动、懒加载);
- 实战项目:从简易项目(如博客、商城)入手,逐步开发中型项目,将知识点融会贯通。
Vue3的学习遵循**"渐进式"**原则,无需一次性掌握所有特性,先掌握基础语法和核心概念,再通过实战逐步进阶。本文的内容足够支撑你完成Vue3的入门开发,后续只需在实战中不断积累,就能快速掌握Vue3并应用到实际项目中。
祝大家学习顺利,早日成为Vue3开发高手!