Vue3语法基础记录
·
Vue3 快速上手学习笔记
基于《Vue3快速上手》整理,适合初学者的 Vue3 入门指南
目标:从零开始掌握 Vue3 核心语法,能独立搭建项目
目录
一、Vue3 简介
1.1 发布背景
- 发布时间:2020年9月18日
- 版本号:3.0,代号 “One Piece”
- 开发规模:4800+ 次提交、40+ 个 RFC、600+ 次 PR、300+ 贡献者
1.2 主要优势
| 特性 | 说明 |
|---|---|
| 性能提升 | 打包大小减少 41%,初次渲染快 55%,更新渲染快 133%,内存减少 54% |
| 源码升级 | 使用 Proxy 代替 defineProperty 实现响应式;重写虚拟 DOM 和 Tree-Shaking |
| 拥抱 TypeScript | 更好的 TS 支持 |
| Composition API | 全新的组合式 API,代码组织更灵活 |
1.3 新特性概览
Composition API(组合式 API):
setupref与reactivecomputed与watch
新的内置组件:
Fragment- 片段Teleport- 传送Suspense- 异步加载
其他变化:
- 新的生命周期钩子
data选项必须声明为函数- 移除
keyCode作为v-on修饰符
二、创建 Vue3 工程
2.1 基于 Vite 创建(推荐)
Vite 是新一代前端构建工具,优势:
- 轻量快速的热重载(HMR)
- 对 TypeScript、JSX、CSS 开箱即用
- 真正的按需编译
# 创建命令
npm create vue@latest
# 具体配置选项
# √ Project name: vue3_test
# √ Add TypeScript? Yes
# √ Add JSX Support? No
# √ Add Vue Router? No
# √ Add Pinia? No
# √ Add Vitest? No
# √ Add ESLint? Yes
# √ Add Prettier? No
2.2 项目结构特点
项目根目录
├── index.html # 入口文件(在项目最外层)
├── src/
│ ├── main.ts # 应用入口
│ ├── App.vue # 根组件
│ └── components/ # 组件目录
└── vite.config.ts # Vite 配置
2.3 创建应用实例
Vue3 通过 createApp 函数创建应用:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
2.4 Vue3 兼容 Vue2 语法
Vue3 向下兼容 Vue2 语法,且模板中可以没有根标签:
<!-- Vue3 模板可以没有根标签 -->
<template>
<h1>标题</h1>
<p>段落</p>
</template>
三、Vue3 核心语法
3.1 Options API vs Composition API
Options API(Vue2 风格):
<script>
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
},
computed: {
double() {
return this.count * 2
}
}
}
</script>
缺点:数据、方法、计算属性分散在不同选项中,维护困难。
Composition(组合式) API(Vue3 推荐):
<script setup>
import { ref, computed } from 'vue'
// 数据
const count = ref(0)
// 计算属性
const double = computed(() => count.value * 2)
// 方法
function increment() {
count.value++
}
</script>
优点:相关功能的代码组织在一起,逻辑更清晰,便于复用。
3.2 setup 函数
基本用法
setup 是 Vue3 的核心配置项,是 Composition API 的"舞台":
<script>
export default {
name: 'Person',
setup() {
// 数据
let name = '张三'
let age = 18
// 方法
function showInfo() {
alert(`我叫${name},今年${age}岁`)
}
// 返回的对象,模板中可以直接使用
return { name, age, showInfo }
}
}
</script>
setup 特点
- 返回对象:对象中的属性和方法可直接在模板中使用
- this 为 undefined:setup 中不能通过 this 访问组件实例
- 执行时机:在
beforeCreate之前执行
setup 语法糖(推荐写法)
<script setup lang="ts" name="Person">
// 直接写代码,无需 return
import { ref } from 'vue'
const name = ref('张三')
const age = ref(18)
function showInfo() {
alert(`我叫${name.value},今年${age.value}岁`)
}
</script>
语法糖优势:
- 无需手动
return - 无需写
export default - 更简洁的代码
3.3 ref - 创建响应式数据(基本类型)
作用
定义响应式变量,适用于基本类型数据。
语法
import { ref } from 'vue'
let xxx = ref(初始值)
示例
<template>
<div class="person">
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年龄+1</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { ref } from 'vue'
// 定义响应式数据
let name = ref('张三')
let age = ref(18)
// 方法
function changeName() {
// JS 中操作 ref 需要 .value
name.value = '李四'
}
function changeAge() {
age.value += 1
}
</script>
重要注意点
| 场景 | 写法 |
|---|---|
| 模板中使用 | {{ name }}(不需要 .value) |
| JS 中读取/修改 | name.value(需要 .value) |
| ref 返回值 | RefImpl 实例对象,.value 才是响应式的 |
3.4 reactive - 创建响应式数据(对象类型)
作用
定义响应式对象,适用于对象类型数据(对象、数组)。
语法
import { reactive } from 'vue'
let 响应式对象 = reactive(源对象)
示例
<template>
<div class="person">
<h2>汽车:{{ car.brand }} - {{ car.price }}万</h2>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<button @click="changeCar">修改汽车</button>
<button @click="changeGame">修改游戏</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { reactive } from 'vue'
// 对象类型数据
let car = reactive({ brand: '奔驰', price: 100 })
// 数组类型数据
let games = reactive([
{ id: '01', name: '英雄联盟' },
{ id: '02', name: '王者荣耀' },
{ id: '03', name: '原神' }
])
function changeCar() {
car.price += 10 // 直接修改,无需 .value
}
function changeGame() {
games[0].name = '流星蝴蝶剑'
}
</script>
特点
reactive定义的数据是深层次响应式的- 修改时不需要
.value - 重新分配对象会失去响应式(可用
Object.assign解决)
3.5 ref vs reactive 对比
| 特性 | ref | reactive |
|---|---|---|
| 数据类型 | 基本类型、对象类型 | 仅对象类型 |
| 访问方式 | 需要 .value |
直接访问 |
| 深层响应 | 对象内部自动用 reactive | 自动深层响应 |
| 重新赋值 | 可以 | 会失去响应式 |
使用原则:
- 基本类型 → 用
ref - 对象类型,层级不深 →
ref或reactive都可以 - 对象类型,层级较深 → 推荐
reactive
3.6 toRefs 与 toRef
作用:将响应式对象的属性转换为 ref,保持响应式。
<script setup lang="ts" name="Person">
import { reactive, toRefs, toRef } from 'vue'
let person = reactive({
name: '张三',
age: 18,
gender: '男'
})
// toRefs:批量转换
let { name, gender } = toRefs(person)
// toRef:单个转换
let age = toRef(person, 'age')
function changeName() {
name.value += '~' // 修改会同步到 person
}
</script>
3.7 computed 计算属性
作用:根据已有数据计算出新数据。
<template>
<div>
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
全名:<span>{{ fullName }}</span> <br>
<button @click="changeFullName">修改全名</button>
</div>
</template>
<script setup lang="ts" name="App">
import { ref, computed } from 'vue'
let firstName = ref('zhang')
let lastName = ref('san')
// 计算属性 - 可读可写
let fullName = computed({
get() {
return firstName.value + '-' + lastName.value
},
set(val) {
const [first, last] = val.split('-')
firstName.value = first
lastName.value = last
}
})
function changeFullName() {
fullName.value = 'li-si'
}
</script>
3.8 watch 监视
作用:监视数据变化。
情况一:监视 ref 定义的基本类型
import { ref, watch } from 'vue'
let sum = ref(0)
// 直接写数据名
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log('sum变化了', newValue, oldValue)
if (newValue >= 10) {
stopWatch() // 停止监视
}
})
情况二:监视 ref 定义的对象类型
let person = ref({
name: '张三',
age: 18
})
// 需要开启深度监视
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue)
}, { deep: true })
情况三:监视 reactive 定义的对象
let person = reactive({ name: '张三', age: 18 })
// 默认开启深度监视
watch(person, (newValue, oldValue) => {
console.log('person变化了')
})
情况四:监视对象中的某个属性
// 属性是基本类型,写成函数式
watch(() => person.name, (newValue, oldValue) => {
console.log('name变化了')
})
// 属性是对象类型,推荐也写成函数式
watch(() => person.car, (newValue, oldValue) => {
console.log('car变化了')
}, { deep: true })
情况五:监视多个数据
watch([() => person.name, person.car], (newValue, oldValue) => {
console.log('多个数据变化了')
}, { deep: true })
3.9 watchEffect
作用:自动追踪依赖,无需明确指出监视哪些数据。
import { ref, watchEffect } from 'vue'
let temp = ref(0)
let height = ref(0)
// 自动追踪 temp 和 height
const stopWatch = watchEffect(() => {
if (temp.value >= 50 || height.value >= 20) {
console.log('联系服务器')
}
// 条件满足时停止监视
if (temp.value === 100 || height.value === 50) {
stopWatch()
}
})
watch vs watchEffect:
watch:明确指定监视的数据watchEffect:自动追踪函数中用到的响应式数据
3.10 标签的 ref 属性
作用:获取 DOM 元素或组件实例。
获取 DOM 元素
<template>
<div>
<h1 ref="title1">节点飞思</h1>
<h2 ref="title2">前端</h2>
<button @click="showLog">打印内容</button>
</div>
</template>
<script setup lang="ts" name="Person">
import { ref } from 'vue'
let title1 = ref()
let title2 = ref()
function showLog() {
console.log(title1.value) // DOM 元素
console.log(title1.value.innerText)
}
</script>
获取组件实例
<!-- 父组件 -->
<template>
<Person ref="ren"/>
<button @click="test">测试</button>
</template>
<script setup lang="ts" name="App">
import Person from './Person.vue'
import { ref } from 'vue'
let ren = ref()
function test() {
console.log(ren.value.name)
console.log(ren.value.age)
}
</script>
<!-- 子组件 Person.vue -->
<script setup lang="ts" name="Person">
import { ref, defineExpose } from 'vue'
let name = ref('张三')
let age = ref(18)
// 必须暴露才能被父组件访问
defineExpose({ name, age })
</script>
3.11 props 父传子
<!-- 父组件 -->
<template>
<Person :list="persons"/>
</template>
<script setup lang="ts" name="App">
import { reactive } from 'vue'
import Person from './Person.vue'
type Persons = Array<{ id: string; name: string; age: number }>
let persons = reactive<Persons>([
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 }
])
</script>
<!-- 子组件 -->
<template>
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }}--{{ item.age }}
</li>
</ul>
</template>
<script setup lang="ts" name="Person">
// 仅接收
// defineProps(['list'])
// 接收 + 限制类型
// defineProps<{ list: Persons }>()
// 接收 + 限制类型 + 默认值
let props = withDefaults(defineProps<{ list?: Persons }>(), {
list: () => [{ id: '001', name: '小猪佩奇', age: 18 }]
})
</script>
3.12 生命周期
Vue3 生命周期钩子:
| 阶段 | Vue2 | Vue3 |
|---|---|---|
| 创建 | beforeCreate / created | setup |
| 挂载 | beforeMount / mounted | onBeforeMount / onMounted |
| 更新 | beforeUpdate / updated | onBeforeUpdate / onUpdated |
| 卸载 | beforeDestroy / destroyed | onBeforeUnmount / onUnmounted |
<script setup lang="ts" name="Person">
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
let sum = ref(0)
console.log('setup')
onBeforeMount(() => console.log('挂载之前'))
onMounted(() => console.log('挂载完毕'))
onBeforeUpdate(() => console.log('更新之前'))
onUpdated(() => console.log('更新完毕'))
onBeforeUnmount(() => console.log('卸载之前'))
onUnmounted(() => console.log('卸载完毕'))
</script>
3.13 自定义 Hook
作用:封装复用逻辑,类似于 Vue2 的 mixin。
// hooks/useSum.ts
import { ref, onMounted } from 'vue'
export default function() {
let sum = ref(0)
const increment = () => { sum.value += 1 }
const decrement = () => { sum.value -= 1 }
onMounted(() => increment())
return { sum, increment, decrement }
}
// hooks/useDog.ts
import { reactive, onMounted } from 'vue'
import axios from 'axios'
export default function() {
let dogList = reactive<string[]>([])
async function getDog() {
try {
let { data } = await axios.get('https://dog.ceo/api/breeds/image/random')
dogList.push(data.message)
} catch (error) {
console.log(error)
}
}
onMounted(() => getDog())
return { dogList, getDog }
}
<!-- 组件中使用 -->
<script setup lang="ts">
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'
let { sum, increment, decrement } = useSum()
let { dogList, getDog } = useDog()
</script>
四、路由
4.1 基本使用
安装 vue-router 4:
npm install vue-router@4
配置路由:
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/home', component: Home },
{ path: '/news', component: News },
{ path: '/about', component: About }
]
})
export default router
注册路由:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
使用路由:
<template>
<div class="app">
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink to="/about" active-class="active">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts" name="App">
import { RouterLink, RouterView } from 'vue-router'
</script>
4.2 路由器工作模式
| 模式 | 优点 | 缺点 |
|---|---|---|
| history | URL 美观,不带 # | 需要服务端配合处理路径 |
| hash | 兼容性好,无需服务端处理 | URL 带 #,SEO 较差 |
// history 模式
const router = createRouter({
history: createWebHistory(),
routes: [...]
})
// hash 模式
const router = createRouter({
history: createWebHashHistory(),
routes: [...]
})
4.3 路由传参
query 参数
<!-- 传递 -->
<RouterLink :to="{
path: '/news/detail',
query: {
id: news.id,
title: news.title
}
}">
{{ news.title }}
</RouterLink>
<!-- 接收 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query.id)
console.log(route.query.title)
</script>
params 参数
<!-- 传递 -->
<RouterLink :to="{
name: 'xiang',
params: {
id: news.id,
title: news.title
}
}">
{{ news.title }}
</RouterLink>
<!-- 路由规则需要占位 -->
{ name: 'xiang', path: 'detail/:id/:title', component: Detail }
<!-- 接收 -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
</script>
4.4 编程式导航
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
// 跳转
function goHome() {
router.push('/home')
}
// 带参数跳转
function goDetail(id) {
router.push({
name: 'detail',
params: { id }
})
}
// 替换当前记录
function replaceHome() {
router.replace('/home')
}
</script>
五、Pinia 状态管理
Pinia 是什么?
Pinia 是 Vue 官方推荐的新一代状态管理库,用来统一管理 Vue 项目里的全局数据。
作用:
- 统一管理全局数据:
多个页面、多个组件都要用的数据(比如用户信息、token、主题、语言),不用层层传递,直接存在 Pinia 里。 - 数据响应式,一处改、处处更新:
在任何页面修改数据,所有用到这个数据的地方自动同步更新,不用手动刷新。
5.1 安装与配置
npm install pinia
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
5.2 定义 Store
// store/count.ts
import { defineStore } from 'pinia'
// 选项式写法
export const useCountStore = defineStore('count', {
// 状态
state() {
return {
sum: 6,
school: '飞思'
}
},
// 计算
getters: {
bigSum: (state) => state.sum * 10,
upperSchool(): string {
return this.school.toUpperCase()
}
},
// 函数方法
actions: {
increment(value: number) {
if (this.sum < 10) {
this.sum += value
}
}
}
})
5.3 组合式写法(推荐)
// store/talk.ts
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import axios from 'axios'
export const useTalkStore = defineStore('talk', () => {
// state
const talkList = reactive([])
// action
async function getATalk() {
let { data } = await axios.get('https://api.uomg.com/api/rand.qinghua')
talkList.unshift(data.content)
}
return { talkList, getATalk }
})
5.4 组件中使用
<template>
<div class="count">
<h2>当前求和为:{{ sum }}</h2>
<h2>学校:{{ school }}</h2>
<h2>大十倍:{{ bigSum }}</h2>
<button @click="increment(1)">+1</button>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
import { storeToRefs } from 'pinia'
const countStore = useCountStore()
// 使用 storeToRefs 保持响应式
const { sum, school, bigSum } = storeToRefs(countStore)
// 方法可以直接解构
const { increment } = countStore
</script>
5.5 修改数据的三种方式
// 方式一:直接修改
countStore.sum = 666
// 方式二:批量修改
countStore.$patch({
sum: 999,
school: '飞思'
})
// 方式三:通过 action 修改(推荐)
// store 中定义 action,组件中调用
countStore.increment(10)
六、组件通信
6.1 props(父 ↔ 子)
<!-- 父组件 -->
<template>
<Child :car="car" :getToy="getToy"/>
</template>
<script setup>
import { ref } from 'vue'
const car = ref('奔驰')
const toy = ref()
function getToy(value) {
toy.value = value
}
</script>
<!-- 子组件 -->
<template>
<h4>父给我的车:{{ car }}</h4>
<button @click="getToy(toy)">玩具给父亲</button>
</template>
<script setup>
import { ref } from 'vue'
const toy = ref('奥特曼')
defineProps(['car', 'getToy'])
</script>
6.2 自定义事件(子 → 父)
<!-- 父组件 -->
<Child @send-toy="handleToy"/>
<script setup>
function handleToy(toy) {
console.log('收到玩具:', toy)
}
</script>
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['send-toy'])
function send() {
emit('send-toy', '奥特曼')
}
</script>
6.3 mitt(任意组件间)
npm install mitt
// utils/emitter.ts
import mitt from 'mitt'
const emitter = mitt()
export default emitter
<!-- 接收方 -->
<script setup>
import emitter from '@/utils/emitter'
import { onUnmounted } from 'vue'
emitter.on('send-toy', (value) => {
console.log('收到:', value)
})
onUnmounted(() => {
emitter.off('send-toy')
})
</script>
<!-- 发送方 -->
<script setup>
import emitter from '@/utils/emitter'
function send() {
emitter.emit('send-toy', '奥特曼')
}
</script>
6.4 v-model(父子双向)
<!-- 父组件 -->
<template>
<JdfsInput v-model="userName"/>
</template>
<script setup>
import { ref } from 'vue'
const userName = ref('张三')
</script>
<!-- 子组件 -->
<template>
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
>
</template>
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
6.5 provide / inject(祖孙通信)
<!-- 祖先组件 -->
<script setup>
import { ref, reactive, provide } from 'vue'
let money = ref(100)
let car = reactive({ brand: '奔驰', price: 100 })
function updateMoney(value) {
money.value += value
}
// 提供数据
provide('moneyContext', { money, updateMoney })
provide('car', car)
</script>
<!-- 孙组件 -->
<script setup>
import { inject } from 'vue'
// 注入数据
let { money, updateMoney } = inject('moneyContext')
let car = inject('car')
</script>
6.6 插槽 Slot
默认插槽
<!-- 父组件 -->
<Category title="今日热门游戏">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
<!-- 子组件 -->
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot></slot> <!-- 默认插槽 -->
</div>
</template>
具名插槽
<!-- 父组件 -->
<Category title="今日热门游戏">
<template v-slot:s1>
<ul>...</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
<!-- 子组件 -->
<template>
<div class="item">
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
作用域插槽
<!-- 父组件 -->
<Game v-slot="params">
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</Game>
<!-- 子组件 -->
<template>
<div class="category">
<h2>今日游戏榜单</h2>
<slot :games="games" a="哈哈"></slot>
</div>
</template>
<script setup>
import { reactive } from 'vue'
let games = reactive([
{ id: '01', name: '英雄联盟' },
{ id: '02', name: '王者荣耀' }
])
</script>
总结
Vue3 学习路线
- 基础阶段:掌握
setup、ref、reactive、computed、watch - 进阶阶段:学习生命周期、组件通信、自定义 Hook
- 工程化:掌握路由(vue-router)、状态管理(Pinia)
- 实战:用 Vue3 + TypeScript 搭建项目
关键记忆点
ref→ 基本类型,需要.valuereactive→ 对象类型,不需要.valuesetup→ Vue3 的核心,Composition API 的舞台script setup→ 推荐写法,更简洁Pinia→ 替代 Vuex,更简单mitt→ 替代事件总线
更多推荐
所有评论(0)