Vue3入门教程 - 从零开始学习Vue3框架
title: Vue3入门教程 - 从零开始学习Vue3框架完整指南
description: 详细的Vue3入门教程,包含环境搭建、基础语法、组合式API、组件系统、路由、状态管理等完整内容,适合前端开发者学习Vue3框架
keywords: Vue3,Vue3教程,Vue3入门,Vue3学习,Vue3框架,组合式API,Composition API,Vue.js,前端框架,Vue3开发
Vue3入门教程 - 从零开始学习Vue3框架
目录
- Vue3简介
- 环境搭建
- 第一个Vue3应用
- 模板语法
- 响应式数据
- 计算属性和侦听器
- 条件渲染和列表渲染
- 事件处理
- 表单绑定
- 组件基础
- 组合式API
- 生命周期钩子
- Props和Emits
- 插槽
- Vue Router路由
- Pinia状态管理
- 实际项目案例
- 最佳实践和总结
1. Vue3简介
Vue.js 3(简称Vue3)是一个用于构建用户界面的渐进式JavaScript框架。它是Vue.js框架的最新版本,于2020年9月正式发布。
核心特性:
- ✅ 性能提升:比Vue2快2倍,体积更小
- ✅ 组合式API:更好的逻辑复用和代码组织
- ✅ TypeScript支持:更好的TypeScript支持
- ✅ 更好的Tree-shaking:更小的打包体积
- ✅ Fragment支持:组件可以有多个根节点
- ✅ Teleport组件:传送门功能
- ✅ Suspense组件:异步组件加载
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 性能 | 基准 | 快2倍 |
| 打包体积 | 基准 | 小41% |
| API风格 | Options API | Composition API + Options API |
| TypeScript | 有限支持 | 原生支持 |
| 响应式系统 | Object.defineProperty | Proxy |
| 组件根节点 | 单个 | 多个 |
- 现代化:Vue3是Vue的未来,新项目都使用Vue3
- 性能优秀:性能大幅提升
- 生态丰富:Vue Router 4、Pinia等配套工具
- 开发体验好:更好的TypeScript支持和开发工具
- 社区活跃:活跃的社区和丰富的资源
2. 环境搭建
- Node.js:版本 16.0 或更高
- npm 或 yarn 或 pnpm 包管理器
- 代码编辑器:推荐 VS Code
访问 Node.js官网 下载并安装最新LTS版本。
验证安装:
node -v
npm -v方式一:使用Vite(推荐)
Vite是Vue3官方推荐的构建工具,速度快,开发体验好。
# 使用npm
npm create vue@latest my-vue-app
# 使用yarn
yarn create vue my-vue-app
# 使用pnpm
pnpm create vue my-vue-app创建过程中会提示选择:
- ✅ TypeScript
- ✅ JSX Support
- ✅ Vue Router
- ✅ Pinia
- ✅ Vitest
- ✅ ESLint
方式二:使用Vue CLI
# 安装Vue CLI
npm install -g @vue/cli
# 创建项目
vue create my-vue-app
# 选择Vue 3预设方式三:使用CDN(学习用)
<!DOCTYPE html>
<html>
<head>
<title>Vue3 CDN示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">{{ message }}</div>
<script>
const { createApp } = Vue
createApp({
data() {
return {
message: 'Hello Vue3!'
}
}
}).mount('#app')
</script>
</body>
</html>使用Vite创建的项目结构:
my-vue-app/
├── public/ # 静态资源
│ └── favicon.ico
├── src/ # 源代码目录
│ ├── assets/ # 资源文件
│ ├── components/ # 组件
│ ├── router/ # 路由配置
│ ├── stores/ # 状态管理
│ ├── views/ # 页面视图
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── index.html # HTML模板
├── package.json # 项目配置
└── vite.config.js # Vite配置2.5 安装依赖并运行
# 进入项目目录
cd my-vue-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 构建生产版本
npm run build访问 http://localhost:5173 查看应用。
2.6 推荐VS Code插件
- Volar:Vue3官方推荐的VS Code扩展
- Vue Language Features (Volar)
- TypeScript Vue Plugin (Volar)
- ESLint:代码检查
- Prettier:代码格式化
3. 第一个Vue3应用
在Vue3中,使用createApp创建应用实例:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')<template>
<div class="app">
<h1>{{ title }}</h1>
<p>欢迎学习Vue3!</p>
<button @click="handleClick">点击我</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
title: '我的第一个Vue3应用'
}
},
methods: {
handleClick() {
alert('按钮被点击了!')
}
}
}
</script>
<style scoped>
.app {
text-align: center;
padding: 20px;
}
h1 {
color: #42b983;
}
</style>Vue单文件组件(.vue)包含三个部分:
<template>
<!-- HTML模板 -->
</template>
<script>
// JavaScript逻辑
</script>
<style>
/* CSS样式 */
</style>4. 模板语法
使用双大括号{{ }}进行文本插值:
<template>
<div>
<p>消息:{{ message }}</p>
<p>数字:{{ number }}</p>
<p>布尔值:{{ isActive }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue3',
number: 42,
isActive: true
}
}
}
</script>使用v-html指令插入原始HTML:
<template>
<div>
<p>{{ rawHtml }}</p>
<p v-html="rawHtml"></p>
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">红色文本</span>'
}
}
}
</script>注意:v-html有XSS攻击风险,只信任的内容使用。
使用v-bind或简写:绑定属性:
<template>
<div>
<!-- 完整写法 -->
<a v-bind:href="url">链接</a>
<!-- 简写 -->
<a :href="url">链接</a>
<!-- 动态类名 -->
<div :class="{ active: isActive }">动态类</div>
<!-- 动态样式 -->
<div :style="{ color: textColor }">动态样式</div>
</div>
</template>
<script>
export default {
data() {
return {
url: 'https://vuejs.org',
isActive: true,
textColor: 'blue'
}
}
}
</script>模板中可以写JavaScript表达式:
<template>
<div>
<p>{{ number + 1 }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<p>{{ isActive ? '激活' : '未激活' }}</p>
<p>{{ new Date().toLocaleString() }}</p>
</div>
</template>
<script>
export default {
data() {
return {
number: 10,
message: 'Hello',
isActive: true
}
}
}
</script>5. 响应式数据
在Options API中,使用data函数返回响应式数据:
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>在组合式API中,使用ref和reactive创建响应式数据:
<template>
<div>
<p>计数:{{ count }}</p>
<p>用户:{{ user.name }} - {{ user.age }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// ref用于基本类型
const count = ref(0)
// reactive用于对象
const user = reactive({
name: '张三',
age: 25
})
function increment() {
count.value++ // ref需要使用.value访问
user.age++ // reactive直接访问
}
</script>| 特性 | ref | reactive |
|---|---|---|
| 类型 | 基本类型和对象 | 仅对象 |
| 访问 | 需要.value | 直接访问 |
| 解构 | 保持响应性 | 失去响应性 |
| 推荐 | 基本类型 | 对象 |
使用toRefs保持响应性:
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
// 解构后保持响应性
const { name, age } = toRefs(state)
</script>6. 计算属性和侦听器
计算属性基于响应式数据计算,有缓存:
<template>
<div>
<p>原价:{{ price }}</p>
<p>数量:{{ quantity }}</p>
<p>总价:{{ totalPrice }}</p>
</div>
</template>
<script>
export default {
data() {
return {
price: 10,
quantity: 5
}
},
computed: {
totalPrice() {
return this.price * this.quantity
}
}
}
</script>组合式API:
<script setup>
import { ref, computed } from 'vue'
const price = ref(10)
const quantity = ref(5)
const totalPrice = computed(() => {
return price.value * quantity.value
})
</script><template>
<div>
<!-- 计算属性:有缓存,只有依赖变化才重新计算 -->
<p>{{ computedValue }}</p>
<p>{{ computedValue }}</p>
<!-- 方法:每次调用都执行 -->
<p>{{ methodValue() }}</p>
<p>{{ methodValue() }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
// 计算属性
const computedValue = computed(() => {
console.log('计算属性执行')
return count.value * 2
})
// 方法
function methodValue() {
console.log('方法执行')
return count.value * 2
}
</script>监听数据变化:
<template>
<div>
<input v-model="message" />
<p>消息:{{ message }}</p>
<p>变化次数:{{ changeCount }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
changeCount: 0
}
},
watch: {
message(newVal, oldVal) {
console.log(`从 ${oldVal} 变为 ${newVal}`)
this.changeCount++
}
}
}
</script>组合式API:
<script setup>
import { ref, watch } from 'vue'
const message = ref('')
const changeCount = ref(0)
watch(message, (newVal, oldVal) => {
console.log(`从 ${oldVal} 变为 ${newVal}`)
changeCount.value++
})
</script>自动追踪依赖:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const double = ref(0)
watchEffect(() => {
double.value = count.value * 2
console.log('watchEffect执行')
})
</script>7. 条件渲染和列表渲染
<template>
<div>
<p v-if="type === 'A'">类型A</p>
<p v-else-if="type === 'B'">类型B</p>
<p v-else>其他类型</p>
<!-- 使用template包装多个元素 -->
<template v-if="isVisible">
<h1>标题</h1>
<p>内容</p>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
const type = ref('A')
const isVisible = ref(true)
</script>v-show只是切换CSS的display属性:
<template>
<div>
<p v-show="isVisible">可见内容</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
</script>v-if vs v-show:
v-if:条件为false时不渲染DOM,切换开销大v-show:总是渲染DOM,切换开销小
<template>
<div>
<!-- 遍历数组 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- 带索引 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
<!-- 遍历对象 -->
<ul>
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
const user = ref({
name: '张三',
age: 25,
city: '北京'
})
</script>重要提示:使用v-for时必须提供:key,且key应该是唯一值。
8. 事件处理
使用v-on或简写@绑定事件:
<template>
<div>
<!-- 完整写法 -->
<button v-on:click="handleClick">点击</button>
<!-- 简写 -->
<button @click="handleClick">点击</button>
<!-- 内联处理 -->
<button @click="count++">计数:{{ count }}</button>
<!-- 传递参数 -->
<button @click="handleClick('参数')">带参数</button>
<!-- 访问事件对象 -->
<button @click="handleEvent($event)">事件对象</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function handleClick(message) {
alert(message || '按钮被点击')
}
function handleEvent(event) {
console.log(event.target)
}
</script><template>
<div>
<!-- .stop 阻止事件冒泡 -->
<div @click="outerClick">
<button @click.stop="innerClick">内部按钮</button>
</div>
<!-- .prevent 阻止默认行为 -->
<form @submit.prevent="onSubmit">
<button type="submit">提交</button>
</form>
<!-- .once 只触发一次 -->
<button @click.once="handleOnce">只触发一次</button>
<!-- .capture 使用捕获模式 -->
<div @click.capture="captureClick">
<button @click="bubbleClick">按钮</button>
</div>
<!-- .self 只有点击自身才触发 -->
<div @click.self="selfClick">
<button>按钮</button>
</div>
<!-- 链式修饰符 -->
<a @click.stop.prevent="doSomething">链接</a>
</div>
</template>
<script setup>
function outerClick() {
console.log('外部点击')
}
function innerClick() {
console.log('内部点击')
}
function onSubmit() {
console.log('表单提交')
}
function handleOnce() {
console.log('只执行一次')
}
function captureClick() {
console.log('捕获阶段')
}
function bubbleClick() {
console.log('冒泡阶段')
}
function selfClick() {
console.log('自身点击')
}
function doSomething() {
console.log('执行操作')
}
</script><template>
<div>
<!-- 按键修饰符 -->
<input @keyup.enter="onEnter" />
<input @keyup.esc="onEsc" />
<input @keyup.delete="onDelete" />
<!-- 系统修饰键 -->
<input @keyup.ctrl.enter="onCtrlEnter" />
<input @keyup.shift.enter="onShiftEnter" />
<!-- 精确修饰符 -->
<input @keyup.ctrl.exact="onCtrlOnly" />
</div>
</template>
<script setup>
function onEnter() {
console.log('Enter键')
}
function onEsc() {
console.log('Esc键')
}
function onDelete() {
console.log('Delete键')
}
function onCtrlEnter() {
console.log('Ctrl+Enter')
}
function onShiftEnter() {
console.log('Shift+Enter')
}
function onCtrlOnly() {
console.log('只有Ctrl')
}
</script>9. 表单绑定
v-model实现表单元素的双向数据绑定:
<template>
<div>
<!-- 文本输入 -->
<input v-model="message" placeholder="输入消息" />
<p>消息:{{ message }}</p>
<!-- 多行文本 -->
<textarea v-model="text"></textarea>
<p>文本:{{ text }}</p>
<!-- 复选框 -->
<input type="checkbox" v-model="checked" />
<p>选中:{{ checked }}</p>
<!-- 多个复选框 -->
<input type="checkbox" value="苹果" v-model="fruits" />
<input type="checkbox" value="香蕉" v-model="fruits" />
<input type="checkbox" value="橙子" v-model="fruits" />
<p>选择的水果:{{ fruits }}</p>
<!-- 单选框 -->
<input type="radio" value="男" v-model="gender" />
<input type="radio" value="女" v-model="gender" />
<p>性别:{{ gender }}</p>
<!-- 下拉选择 -->
<select v-model="selected">
<option value="">请选择</option>
<option value="选项1">选项1</option>
<option value="选项2">选项2</option>
</select>
<p>选择:{{ selected }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
const text = ref('')
const checked = ref(false)
const fruits = ref([])
const gender = ref('')
const selected = ref('')
</script><template>
<div>
<!-- .lazy 失焦时更新 -->
<input v-model.lazy="message" />
<!-- .number 转为数字 -->
<input v-model.number="age" type="number" />
<!-- .trim 去除首尾空格 -->
<input v-model.trim="username" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
const age = ref(0)
const username = ref('')
</script><!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>使用:
<template>
<CustomInput v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const message = ref('')
</script>10. 组件基础
<!-- Button.vue -->
<template>
<button class="btn" @click="handleClick">
<slot></slot>
</button>
</template>
<script setup>
const emit = defineEmits(['click'])
function handleClick() {
emit('click')
}
</script>
<style scoped>
.btn {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style><template>
<div>
<Button @click="onButtonClick">点击我</Button>
</div>
</template>
<script setup>
import Button from './components/Button.vue'
function onButtonClick() {
alert('按钮被点击')
}
</script>全局注册:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Button from './components/Button.vue'
const app = createApp(App)
app.component('Button', Button)
app.mount('#app')局部注册:
<script setup>
import Button from './components/Button.vue'
// 直接使用,无需注册
</script>11. 组合式API
<script setup>是组合式API的语法糖:
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script><script setup>
import { ref, reactive, computed, watch } from 'vue'
// ref:基本类型
const count = ref(0)
// reactive:对象
const state = reactive({
name: 'Vue3',
version: '3.0'
})
// computed:计算属性
const doubleCount = computed(() => count.value * 2)
// watch:侦听器
watch(count, (newVal, oldVal) => {
console.log(`从 ${oldVal} 变为 ${newVal}`)
})
</script>创建可复用的逻辑:
// composables/useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}使用:
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './composables/useCounter'
const { count, increment, decrement, reset } = useCounter(10)
</script>12. 生命周期钩子
创建阶段:beforeCreate → created → beforeMount → mounted
更新阶段:beforeUpdate → updated
销毁阶段:beforeUnmount → unmounted<script>
export default {
data() {
return {
message: 'Hello'
}
},
beforeCreate() {
console.log('beforeCreate:实例创建前')
},
created() {
console.log('created:实例创建后')
},
beforeMount() {
console.log('beforeMount:挂载前')
},
mounted() {
console.log('mounted:挂载后')
},
beforeUpdate() {
console.log('beforeUpdate:更新前')
},
updated() {
console.log('updated:更新后')
},
beforeUnmount() {
console.log('beforeUnmount:卸载前')
},
unmounted() {
console.log('unmounted:卸载后')
}
}
</script><script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
onBeforeMount(() => {
console.log('挂载前')
})
onMounted(() => {
console.log('挂载后')
// 通常在这里进行DOM操作、API调用等
})
onBeforeUpdate(() => {
console.log('更新前')
})
onUpdated(() => {
console.log('更新后')
})
onBeforeUnmount(() => {
console.log('卸载前')
// 清理定时器、取消订阅等
})
onUnmounted(() => {
console.log('卸载后')
})
</script>13. Props和Emits
子组件:
<!-- Child.vue -->
<template>
<div>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
</div>
</template>
<script setup>
// 定义props
const props = defineProps({
name: {
type: String,
required: true
},
age: {
type: Number,
default: 0
}
})
// 使用props
console.log(props.name)
</script>父组件:
<template>
<Child name="张三" :age="25" />
</template>
<script setup>
import Child from './Child.vue'
</script>子组件:
<!-- Child.vue -->
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
const emit = defineEmits(['update', 'delete'])
function handleClick() {
emit('update', '新数据')
emit('delete', 123)
}
</script>父组件:
<template>
<Child
@update="handleUpdate"
@delete="handleDelete"
/>
</template>
<script setup>
import Child from './Child.vue'
function handleUpdate(data) {
console.log('更新:', data)
}
function handleDelete(id) {
console.log('删除:', id)
}
</script>子组件:
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>父组件:
<template>
<CustomInput v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const message = ref('')
</script>14. 插槽
子组件:
<!-- Card.vue -->
<template>
<div class="card">
<slot></slot>
</div>
</template>父组件:
<template>
<Card>
<p>这是卡片内容</p>
</Card>
</template>子组件:
<!-- Layout.vue -->
<template>
<div class="layout">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>父组件:
<template>
<Layout>
<template #header>
<h1>标题</h1>
</template>
<p>主要内容</p>
<template #footer>
<p>页脚</p>
</template>
</Layout>
</template>子组件:
<!-- List.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item"></slot>
</li>
</ul>
</template>
<script setup>
defineProps({
items: Array
})
</script>父组件:
<template>
<List :items="users">
<template #default="{ item }">
<span>{{ item.name }} - {{ item.age }}</span>
</template>
</List>
</template>
<script setup>
import { ref } from 'vue'
import List from './List.vue'
const users = ref([
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
])
</script>15. Vue Router路由
npm install vue-router@4创建路由:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router在main.js中使用:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')App.vue:
<template>
<div id="app">
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<router-view />
</div>
</template><script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function goToAbout() {
router.push('/about')
// 或
router.push({ name: 'About' })
}
</script>定义路由:
{
path: '/user/:id',
name: 'User',
component: User
}获取参数:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const userId = route.params.id
</script>// 全局前置守卫
router.beforeEach((to, from, next) => {
// 检查登录状态
if (to.path === '/admin' && !isLoggedIn) {
next('/login')
} else {
next()
}
})16. Pinia状态管理
npm install pinia在main.js中使用:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 状态
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
function decrement() {
count.value--
}
return {
count,
doubleCount,
increment,
decrement
}
})<template>
<div>
<p>计数:{{ counterStore.count }}</p>
<p>双倍:{{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
</script>17. 实际项目案例
<!-- TodoList.vue -->
<template>
<div class="todo-list">
<h1>待办事项</h1>
<div class="input-section">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="输入待办事项"
/>
<button @click="addTodo">添加</button>
</div>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input
type="checkbox"
v-model="todo.completed"
/>
<span :class="{ completed: todo.completed }">
{{ todo.text }}
</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<p>总计:{{ todos.length }},已完成:{{ completedCount }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const newTodo = ref('')
const todos = ref([
{ id: 1, text: '学习Vue3', completed: false },
{ id: 2, text: '完成项目', completed: false }
])
const completedCount = computed(() => {
return todos.value.filter(t => t.completed).length
})
function addTodo() {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value,
completed: false
})
newTodo.value = ''
}
}
function removeTodo(id) {
const index = todos.value.findIndex(t => t.id === id)
if (index > -1) {
todos.value.splice(index, 1)
}
}
</script>
<style scoped>
.todo-list {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.completed {
text-decoration: line-through;
color: #999;
}
.input-section {
margin-bottom: 20px;
}
input[type="text"] {
padding: 8px;
margin-right: 10px;
width: 300px;
}
button {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
ul {
list-style: none;
padding: 0;
}
li {
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
gap: 10px;
}
</style>18. 最佳实践和总结
src/
├── components/ # 可复用组件
│ ├── common/ # 通用组件
│ └── business/ # 业务组件
├── views/ # 页面组件
├── composables/ # 组合式函数
├── stores/ # 状态管理
├── router/ # 路由配置
├── utils/ # 工具函数
├── assets/ # 静态资源
└── App.vue # 根组件- 组件名:PascalCase(如:UserProfile.vue)
- 文件名:kebab-case(如:user-profile.vue)
- 变量名:camelCase
- 常量名:UPPER_SNAKE_CASE
使用v-show替代v-if(频繁切换时)
合理使用key(v-for中)
计算属性缓存(避免重复计算)
懒加载路由
组件懒加载
忘记使用.value(ref在模板外)
直接修改props(应该用emit)
忘记key(v-for中)
过度使用响应式(不需要响应式的数据)
- 官方文档:https://cn.vuejs.org/
- Vue Router:https://router.vuejs.org/zh/
- Pinia:https://pinia.vuejs.org/zh/
- Vite:https://cn.vitejs.dev/
通过本教程,你已经掌握了:
- ✅ Vue3基础语法和概念
- ✅ 组合式API的使用
- ✅ 组件开发和通信
- ✅ 路由和状态管理
- ✅ 实际项目开发
下一步学习:
- TypeScript集成:学习在Vue3中使用TypeScript
- 测试:学习单元测试和E2E测试
- 性能优化:深入学习性能优化技巧
- 构建工具:深入了解Vite和构建配置
- 服务端渲染:学习Nuxt.js
结语
Vue3是一个功能强大、易于学习的前端框架。通过本教程的学习,相信你已经掌握了Vue3的核心概念和使用方法。
记住:
- 多实践:通过实际项目巩固知识
- 理解原理:理解响应式系统和虚拟DOM
- 关注性能:注意性能优化
- 持续学习:关注Vue生态和最佳实践
祝你学习愉快,开发顺利! 🚀
本教程由Java突击队学习社区编写,如有问题欢迎反馈。