前端笔记-Vue3(上)
学习参考视频:尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程_哔哩哔哩_bilibili
vue3学习目标:
VUE 3 | 1、Vue3架构与设计理念 |
2、组合式API(Composition API) | |
3、常用API:ref、reactive、watch、computed | |
4、Vue3的生命周期 | |
5、组件间通信(props、emit、defineModel) | |
6、了解插槽 |
Vue3简介
1. 核心特性
- 组合式 API (Composition API):替代Vue 2的选项式API,提供更灵活的代码组织方式
- 性能提升:比Vue 2快2倍,包体积小41%
- 更好的TypeScript支持:完整的类型定义
- 新的响应式系统:基于Proxy实现,性能更好
- Fragment/Teleport/Suspense:新增内置组件
2. 主要改进
特性 | Vue 2 | Vue 3 |
---|---|---|
响应式系统 | Object.defineProperty | Proxy |
代码组织 | 选项式API | 组合式API |
虚拟DOM | 全量对比 | 静态标记+动态对比 |
打包体积 | 完整版23kb | 最小10kb |
创建Vue 3项目
具体可以查看一下官方文档:快速上手 | Vue.js
1. 通过Vite创建(推荐)
基于vite构建(类似于webpack,比webpack还快),对TS,JSX,CSS支持开箱即用。
npm create vite@latest my-vue-app --template vue
cd my-vue-app
npm install
npm run dev
2. 通过Vue CLI创建
npm install -g @vue/cli
vue create my-vue-app
# 选择Vue 3预设
cd my-vue-app
npm run serve
Vue 3生命周期对比
Vue 2选项 | Vue 3组合式API | 说明 |
---|---|---|
beforeCreate | setup() | 组件初始化前 |
created | setup() | 组件初始化后 |
beforeMount | onBeforeMount | DOM挂载前 |
mounted | onMounted | DOM挂载后 |
beforeUpdate | onBeforeUpdate | 数据更新前 |
updated | onUpdated | 数据更新后 |
beforeUnmount | onBeforeUnmount | 组件卸载前 |
unmounted | onUnmounted | 组件卸载后 |
组合式API
特性 | 选项式 API (Options API) | 组合式 API (Composition API) |
---|---|---|
代码组织方式 | 按选项类型分组 | 按逻辑功能组织 |
复用机制 | Mixins/作用域插槽 | 组合式函数 |
响应式系统 | 自动响应式 | 显式声明响应式 |
this 使用 | 需要 | 不需要 |
TypeScript 支持 | 有限 | 优秀 |
学习曲线 | 平缓 | 较陡 |
适用场景 | 简单组件/新手友好 | 复杂组件/大型项目 |
1. 代码组织方式
选项式 API:
export default {data() {return {count: 0,searchQuery: ''}},computed: {doubleCount() {return this.count * 2}},methods: {increment() {this.count++}},mounted() {console.log('组件已挂载')}
}
组合式 API:
需要注意的是setup中的this是undefined
import { ref, computed, onMounted } from 'vue'export default {setup() {const count = ref(0)const searchQuery = ref('')const doubleCount = computed(() => count.value * 2)function increment() {count.value++}onMounted(() => {console.log('组件已挂载')})return {count,searchQuery,doubleCount,increment}}
}
2. 逻辑复用实现
略
3. 响应式系统差异
选项式 API:
- 自动将
data()
返回的对象转为响应式 - 计算属性和方法自动绑定
this
上下文
组合式 API:
- 需要显式使用
ref()
或reactive()
创建响应式数据 - 需要手动暴露给模板使用的变量和方法
同样的例子使用vue2语法写过之后无法在vue3里面实现响应式变化(还有后续)
<template><div class="student"><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{studentAge}}</h2><h2>数学成绩:{{mathScore}}</h2><button @click="updateName">修改姓名</button><button @click="increaseAge">增加年龄</button><button @click="improveScore">提高成绩</button></div>
</template><script>
export default {name: 'Student',setup() {// 普通变量(非响应式)let studentName = '李四'let studentAge = 16let mathScore = 85// 方法function updateName() {studentName = 'Li Si' // 修改不会反映到视图中console.log('姓名已改为:', studentName)}function increaseAge() {studentAge++ // 修改不会反映到视图中console.log('年龄已增加至:', studentAge)}function improveScore() {mathScore += 5 // 修改不会反映到视图中console.log('数学成绩已提高至:', mathScore)}// 返回这些变量和方法return {studentName,studentAge,mathScore,updateName,increaseAge,improveScore}}
}
</script><style scoped>
.student {background-color: #f5f5f5;padding: 20px;margin: 20px;border-radius: 8px;
}
button {margin: 0 10px;padding: 5px 10px;
}
</style>
4. 生命周期对应关系
选项式 API | 组合式 API |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | on |
5.setup 的返回值
- 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用
- 若返回一个函数:则可以自定义渲染内容,代码如下:(因为没有this)
setup(){return ()=> 'Hello World'
}
6. setup与Options API 的关系
Vue2
的配置(data
、methos
......)中可以访问到setup
中的属性、方法。- 但在
setup
中不能访问到Vue2
的配置(data
、methos
......)。 - 如果与
Vue2
冲突,则setup
优先。
7.setup 语法糖
使用setup
语法糖,可以使我们使用vue3编写脚本时更加方便:
<template><div class="student"><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{studentAge}}</h2><h2>数学成绩:{{mathScore}}</h2><button @click="updateName">修改姓名</button><button @click="increaseAge">增加年龄</button><button @click="improveScore">提高成绩</button></div>
</template><script setup>
// 普通变量(非响应式)
let studentName = '李四'
let studentAge = 16
let mathScore = 85// 方法
function updateName() {studentName = 'Li Si' // 修改不会反映到视图中console.log('姓名已改为:', studentName)
}function increaseAge() {studentAge++ // 修改不会反映到视图中console.log('年龄已增加至:', studentAge)
}function improveScore() {mathScore += 5 // 修改不会反映到视图中console.log('数学成绩已提高至:', mathScore)
}
</script><style scoped>
.student {background-color: #f5f5f5;padding: 20px;margin: 20px;border-radius: 8px;
}
button {margin: 0 10px;padding: 5px 10px;
}
</style>
我们现在只需要写数据和方法即可,
这时候<script setup>和export default{}不能再共用,但是可以同时存在两个script(一个是用来配置名字,一个是关键配置文件)
当然还可以再简单一点,
上述代码,还需要编写一个不写
setup
的script
标签,去指定组件名字,比较麻烦,我们可以借助vite
中的插件简化
- 第一步:
npm i vite-plugin-vue-setup-extend -D
- 第二步:
vite.config.ts
import { defineConfig } from 'vite' import VueSetupExtend from 'vite-plugin-vue-setup-extend'export default defineConfig({plugins: [ VueSetupExtend() ] })
- 第三步:
<script setup lang="ts" name="Person">
ref 创建:基本类型的响应式数据
要使数据呈现响应式变化,可以引入ref创建响应式数据
关键区别对比
特性 | 非响应式版本 | 响应式版本 (setup语法糖) |
---|---|---|
语法 | 传统 setup 函数 | <script setup> 语法糖 |
响应式 | ❌ 数据变化不更新视图 | ✔️ 数据变化自动更新视图 |
变量声明 | 普通变量 (let ) | ref() 响应式引用 |
数据访问 | 直接访问 (studentName ) | 通过 .value 访问 (studentName.value ) |
代码简洁度 | 需要显式 return | 自动暴露顶层绑定,无需 return |
TypeScript支持 | 有限 | 更好的类型推断 |
组件选项 | 可以混合使用其他选项 | 纯组合式API风格 |
还是之前的例子,我们加入ref实现响应式基本类型的响应式变化
<template><div class="student"><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{studentAge}}</h2><h2>数学成绩:{{mathScore}}</h2><button @click="updateName">修改姓名</button><button @click="increaseAge">增加年龄</button><button @click="improveScore">提高成绩</button></div>
</template><script setup>
import { ref } from 'vue'// 响应式变量
const studentName = ref('李四')
const studentAge = ref(16)
const mathScore = ref(85)// 方法
function updateName() {studentName.value = 'Li Si' // 修改会实时反映到视图console.log('姓名已改为:', studentName.value)
}function increaseAge() {studentAge.value++ // 修改会实时反映到视图console.log('年龄已增加至:', studentAge.value)
}function improveScore() {mathScore.value += 5 // 修改会实时反映到视图console.log('数学成绩已提高至:', mathScore.value)
}
</script><style scoped>
.student {background-color: #f5f5f5;padding: 20px;margin: 20px;border-radius: 8px;
}
button {margin: 0 10px;padding: 5px 10px;
}
</style>
点击按钮,数据变化成功。
在这个背后,ref 通过 .value
属性实现响应式追踪:
- 创建时:
ref(16)
→{ value: 16 }
- 修改时:检测
.value
变化触发更新 - 模板中:自动解包
.value
另外, 基本类型优先使用ref,对于对象类型我们还可以选择reactive。
reactive 创建:对象类型的响应式数据
当你把一个普通 JavaScript 对象传入 reactive()
,它会返回该对象的响应式代理。
- 专为对象/数组设计
- 深度响应式(嵌套对象也是响应式的)
- 修改属性时自动触发视图更新
- 比
ref
更适合处理复杂对象结构
使用reactive的组合式API版本
<template><div class="user-card"><h2>{{ user.name }}</h2><p>年龄: {{ user.age }}</p><p>城市: {{ user.address.city }}</p><button @click="growOlder">长大一岁</button><button @click="moveCity">搬到上海</button></div>
</template><script setup>
import { reactive } from 'vue'// 响应式对象
const user = reactive({name: '张三',age: 25,address: {city: '北京'}
})function growOlder() {user.age++ // 自动更新视图
}function moveCity() {user.address.city = '上海' // 自动更新视图(深度响应式)
}
</script>
reactive深度响应式特性
顾名思义,不管数据藏得有多深,只要用reative包裹起来,就会实现响应式的改变。
<template><div class="cart"><h2>购物车 ({{ cart.items.length }}件)</h2><ul><li v-for="(item, index) in cart.items" :key="index">{{ item.name }} - ¥{{ item.price }}<button @click="removeItem(index)">删除</button></li></ul><p>总价: ¥{{ cart.total }}</p><button @click="addRandomItem">添加随机商品</button></div>
</template><script setup>
import { reactive, computed } from 'vue'// 购物车状态
const cart = reactive({items: [{ name: '商品1', price: 100 },{ name: '商品2', price: 200 }],// 计算属性get total() {return this.items.reduce((sum, item) => sum + item.price, 0)}
})// 添加商品
function addRandomItem() {const randomId = Math.floor(Math.random() * 1000)cart.items.push({name: `商品${randomId}`,price: Math.floor(Math.random() * 500)})
}// 删除商品
function removeItem(index) {cart.items.splice(index, 1)
}
</script>
同样,ref也能处理对象数据类型的响应式变化
<template><div class="cart"><h2>购物车 ({{ cart.items.length }}件)</h2><ul><li v-for="(item, index) in cart.items" :key="index">{{ item.name }} - ¥{{ item.price }}<button @click="removeItem(index)">删除</button></li></ul><p>总价: ¥{{ total }}</p><button @click="addRandomItem">添加随机商品</button></div>
</template><script setup>
import { ref, computed } from 'vue'// 使用 ref 创建购物车状态
const cart = ref({items: [{ name: '商品1', price: 100 },{ name: '商品2', price: 200 }]
})// 计算总价(使用独立的计算属性)
const total = computed(() => {return cart.value.items.reduce((sum, item) => sum + item.price, 0)
})// 添加商品
function addRandomItem() {const randomId = Math.floor(Math.random() * 1000)cart.value.items.push({name: `商品${randomId}`,price: Math.floor(Math.random() * 500)})
}// 删除商品
function removeItem(index) {cart.value.items.splice(index, 1)
}
</script>
ref
与 reactive
关键对比
特性 | reactive 实现 | ref 实现 |
---|---|---|
创建方式 | const cart = reactive({...}) | const cart = ref({...}) |
数据访问 | 直接访问属性:cart.items | 需要通过 .value 访问:cart.value.items |
计算属性 | 可以定义在对象内部作为 getter | 需要单独定义计算属性 |
模板使用 | 直接使用对象属性:{{ cart.total }} | 直接使用 ref 对象(自动解包):{{ cart.items }} |
修改数据 | 直接修改属性:cart.items.push() | 需要通过 .value 修改:cart.value.items.push() |
类型支持 | 对复杂对象类型推断更友好 | 对基本类型更友好,对象类型需要额外处理 |
适用场景 | 适合管理复杂的、嵌套的对象状态 | 适合管理单个值或需要替换整个对象的情况 |
关键差异详解
1. 数据访问方式不同
// 创建
const cart = reactive({ items: [...] })// 访问
cart.items.push(item) // 直接访问
ref
:
// 创建
const cart = ref({ items: [...] })// 访问
cart.value.items.push(item) // 需要通过 .value
2. 计算属性的处理
reactive
可以在对象内部定义:
const cart = reactive({items: [...],get total() {return this.items.reduce(...)}
})
ref
需要单独定义:
const cart = ref({ items: [...] })
const total = computed(() => cart.value.items.reduce(...))
3. 模板中的使用差异
虽然模板中都可以直接使用,但原理不同:
reactive
对象的属性直接暴露ref
在模板中会自动解包,不需要写.value
4. 整体替换的区别
reactive
的限制:
const state = reactive({ count: 0 })
state = { count: 1 } // ❌ 会失去响应式
ref
的优势:
const state = ref({ count: 0 })
state.value = { count: 1 } // ✅ 可以整体替换
这里扩展一下,在settings里面找到Extension(扩展)里面可以设置自动带上.value
更新reactive不可以整体改变,可以借助assign,但是ref可以直接在function里面更新(当然要带上value)。
补充:ToRef和ToRefs
直接解构 vs toRefs
解构对比
1. 直接解构(失去响应式)
let { name, gender } = person
特点:
- 失去响应式:解构出来的
name
和gender
是普通变量,不再是响应式数据。 - 修改无效:即使修改
name
或gender
,不会触发 Vue 的更新机制,界面不会自动刷新。 - 不影响原对象:修改解构后的变量不会影响
person
的原始数据。
示例:
let { name, gender } = person
name = "李四" // 修改无效,不会更新界面,也不会影响 person.name
console.log(person.name) // 仍然是 "张三"
2. toRefs
解构(保持响应式)
let { name, gender } = toRefs(person)
特点:
- 保持响应式:解构出来的
name
和gender
是ref
对象,仍然是响应式数据。 - 修改有效:修改
name.value
或gender.value
,会触发 Vue 的更新机制,界面会自动刷新。 - 双向绑定:修改解构后的变量会影响
person
的原始数据,反之亦然。
示例:
let { name, gender } = toRefs(person)
name.value = "李四" // 修改有效,界面会更新,person.name 也会变成 "李四"
console.log(person.name) // "李四"
对比总结
特性 | 直接解构 let {name} = person | toRefs 解构 let {name} = toRefs(person) |
---|---|---|
响应式 | ❌ 失去响应式 | ✅ 保持响应式 |
修改方式 | name = "新值" (普通赋值) | name.value = "新值" (ref 语法) |
是否影响原对象 | ❌ 不影响 | ✅ 双向绑定,影响原对象 |
适用场景 | 仅需读取数据,不修改 | 需要修改数据并保持响应式 |
toRefs
的使用
-
批量解构响应式属性
let {name, gender} = toRefs(person)
- 从
reactive
对象person
中批量解构出name
和gender
属性 - 解构后的变量仍然保持响应式
- 每个解构出来的属性都是一个
ref
对象,需要通过.value
访问/修改
- 从
-
使用场景
- 当需要从
reactive
对象中解构多个属性时 - 保持解构后的变量仍然是响应式的
- 当需要从
toRef
的使用
-
单个属性解构
let age = toRef(person, 'age')
- 从
reactive
对象person
中单独解构出age
属性 - 解构后的变量仍然保持响应式
- 需要通过
.value
访问/修改
- 从
-
使用场景
- 当只需要从
reactive
对象中解构单个属性时 - 比
toRefs
更精确地控制要解构的属性
- 当只需要从
重要注意事项
-
value
访问name.value += '~' // 正确 name += '~' // 错误,会失去响应式
-
与直接解构的区别
// 错误方式 - 会失去响应式 let {name} = person// 正确方式 - 保持响应式 let {name} = toRefs(person)
-
模板中使用
- 在模板中可以直接使用,不需要
.value
<h2>姓名:{{name}}</h2> <!-- 正确,自动解包 -->
- 在模板中可以直接使用,不需要
-
修改原始对象
- 通过
toRef/toRefs
解构的属性和原对象属性是双向绑定的 - 修改解构后的变量会影响原对象,反之亦然
- 通过
总结对比
函数 | 作用 | 适用场景 |
---|---|---|
toRef | 为 reactive 对象的单个属性创建 ref | 只需要解构单个属性时 |
toRefs | 为 reactive 对象的所有属性创建 ref | 需要解构多个或所有属性时 |