注:纯手打,如有错误欢迎评论区交流!
转载请注明出处:https://blog.csdn.net/testleaf/article/details/148056625
编写此文是为了更好地学习前端知识,如果损害了有关人的利益,请联系删除!
本文章将不定时更新,敬请期待!!!
欢迎点赞、收藏、转发、关注,多谢!!!
目录
一、Vue2选项式API和Vue3组合式API
1、Vue 2 选项式 API(Options API)
<template>
<div>
<p>{{ message }}</p>
<button @click="reverseMessage">Reverse</button>
</div>
</template>
<script>
export default {
data() {
return { message: "Hello Vue 2!" };
},
methods: {
reverseMessage() {
this.message = this.message.split('').reverse().join('');
}
},
mounted() {
console.log("Component mounted!");
}
};
</script>
2、Vue 3 组合式 API(Composition API)
<template>
<div>
<p>{{ message }}</p>
<button @click="reverseMessage">Reverse</button>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const message = ref("Hello Vue 3!");
const reverseMessage = () => {
message.value = message.value.split('').reverse().join('');
};
onMounted(() => {
console.log("Component mounted!");
});
return { message, reverseMessage };
}
};
</script>
3、核心对比
对比维度 | Vue 2 选项式 API | Vue 3 组合式 API |
---|---|---|
代码组织方式 | 按选项(data 、methods 等)分类 | 按逻辑功能聚合(setup() 函数内) |
逻辑复用 | 依赖 mixins (易冲突) | 自定义组合函数(无冲突) |
TypeScript 支持 | 较弱(this 类型推断困难) | 优秀(ref、reactive 类型明确) |
适用场景 | 简单组件、小型项目 | 复杂组件、大型项目、逻辑复用需求 |
响应式系统 | 基于 Object.defineProperty (Vue 2) | 基于 Proxy (Vue 3,性能更好) |
二、Vue3相比于Vue2的优势
1、更容易维护:组合式API、更好的TypeScript
支持;
2、更快的速度:重写diff
算法、模板编译优化、更高效的组件初始化;
3、更优的数据响应式:Proxy
;
4、更小的体积:良好的TreeShaking
、按需引入;
三、使用create-vue搭建Vue3项目
1、认识create-vue
create-vue
是Vue官方新的脚手架工具,底层切换到了 vite
(下一代前端工具链),为开发提供极速响应。
而vue-cli
的底层是webpack
。
2、使用create-vue
创建项目
npm init vue@latest
3、项目关键文件
vite.config.js
- 项目的配置文件:基于vite
的配置package.json
- 项目包文件:核心依赖项变成了Vue3.x
和vite
main.js
- 入口文件:createApp
函数创建应用实例app.vue
- 根组件:SFC
单文件组件script - template - style
- 变化一:脚本
script
和模板template
顺序调整 - 变化二:模板
template
不再要求唯一根元素 - 变化三:脚本
script
添加setup
标识支持组合式API
- 变化一:脚本
index.html
- 单页入口:提供id
为app
的挂载点
四、组合式API - setup选项
1、写法
<script>
export default {
setup(){
},
beforeCreate(){
}
}
</script>
2、执行时机
在beforeCreate
钩子之前执行;
3、setup
中写代码的特点
在setup
函数中写的数据和方法需要在末尾以对象的方式return
,才能给模版使用;
4、<script setup>
语法糖
script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句;
<script setup>
const message = 'this is message'
const logMessage = ()=>{
console.log(message)
}
</script>
五、组合式API - reactive和ref函数
1、reactive
接受对象类型数据的参数传入并返回一个响应式的对象;
import { reactive } from 'vue'
const state = reactive({
count: 0,
message: 'Hello Vue!'
})
// 访问和修改
state.count++
console.log(state.message)
2、ref
接收简单类型或者对象类型的数据传入并返回一个响应式的对象;
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello Vue!')
// 访问和修改
count.value++ // 注意需要通过.value访问
console.log(message.value)
3、比较与选择
特性 | reactive | ref |
---|---|---|
适用类型 | 对象 | 任意值 |
访问方式 | 直接访问 | .value |
模板中使用 | 直接使用 | 自动解包 |
解构响应性 | 丢失 | 保持 |
4、使用建议
- 当需要管理一组相关状态时,使用
reactive
- 当需要管理单个原始值时,使用
ref
- 在组合式函数中返回状态时,通常使用
ref
以保持灵活性
六、组合式API - computed
计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法;
基本用法:
import { ref, computed } from 'vue'
const count = ref(0)
// 创建一个计算属性
const doubleCount = computed(() => count.value * 2)
console.log(doubleCount.value) // 0
count.value++
console.log(doubleCount.value) // 2
可写计算属性:
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
fullName.value = 'Jane Smith' // 会自动更新firstName和lastName
七、组合式API - watch
1、基本用法
import { ref, watch } from 'vue'
const count = ref(0)
// 基本watch用法
watch(count, (newValue, oldValue) => {
console.log(`count从${oldValue}变为${newValue}`)
})
2、观察多个源
const firstName = ref('')
const lastName = ref('')
// 观察多个ref
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`名字从${oldFirst} ${oldLast}变为${newFirst} ${newLast}`)
})
3、观察响应式对象
const state = reactive({
user: {
name: 'Alice',
age: 25
}
})
// 深度观察对象
watch(
() => state.user,
(newUser, oldUser) => {
console.log('用户信息变化:', newUser)
},
{ deep: true }
)
4、立即执行
const data = ref(null)
// 立即执行回调
watch(
data,
(newValue) => {
console.log('数据:', newValue)
},
{ immediate: true }
)
5、观察getter函数
const state = reactive({
items: [],
total: 0
})
// 观察计算值
watch(
() => state.items.length,
(newLength) => {
console.log(`项目数量变为: ${newLength}`)
}
)
6、停止观察
const stop = watch(count, (newValue) => {
if (newValue > 10) {
stop() // 停止观察
console.log('已达到最大值,停止观察')
}
})
7、实际应用示例
<template>
<div>
<input v-model="searchQuery" placeholder="搜索...">
<div v-if="isSearching">搜索中...</div>
<ul v-else>
<li v-for="result in searchResults" :key="result.id">
{{ result.name }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)
// 防抖搜索
watch(searchQuery, async (newQuery) => {
if (!newQuery.trim()) {
searchResults.value = []
return
}
isSearching.value = true
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 300))
searchResults.value = await mockSearch(newQuery)
} finally {
isSearching.value = false
}
})
async function mockSearch(query) {
// 模拟API返回
return [
{ id: 1, name: `${query} 结果1` },
{ id: 2, name: `${query} 结果2` }
]
}
</script>
8、watchEffect
与watch相关的还有watchEffect,它会立即执行一次,并自动追踪其依赖:
import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => {
console.log(`count的值是: ${count.value}`)
})
9、比较watch和watchEffect
特性 | watch | watchEffect |
---|---|---|
惰性执行 | 是 | 否(立即执行) |
指定观察源 | 需要 | 自动追踪 |
访问旧值 | 可以 | 不可以 |
初始化执行 | 需要配置immediate | 总是执行 |
10、最佳实践
- 避免过度使用:优先使用计算属性,只在需要副作用时使用
watch
; - 清理副作用:在回调中执行异步操作时,注意清理之前的操作;
- 性能优化:对于复杂对象,考虑使用
{ deep: true }
或特定getter
函数; - 组件卸载时:在
setup()
中创建的watch
会自动停止,手动创建的记得清理;
八、组合式API - 生命周期函数
在Vue 3的组合式API中,生命周期钩子是通过特定的函数来访问的,而不是Options API中的选项形式。
1、主要生命周期函数
onBeforeMount 和 onMounted:
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => {
console.log('组件即将挂载')
})
onMounted(() => {
console.log('组件已挂载')
// 可以访问DOM元素
})
onBeforeUpdate 和 onUpdated:
import { onBeforeUpdate, onUpdated } from 'vue'
onBeforeUpdate(() => {
console.log('组件即将更新')
})
onUpdated(() => {
console.log('组件已更新')
// DOM已更新完成
})
onBeforeUnmount 和 onUnmounted:
import { onBeforeUnmount, onUnmounted } from 'vue'
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
onUnmounted(() => {
console.log('组件已卸载')
// 清理定时器、事件监听等
})
onErrorCaptured:
import { onErrorCaptured } from 'vue'
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
// 返回false阻止错误继续向上传播
return false
})
2、新增的生命周期函数
Vue 3还引入了两个新的生命周期函数:
onRenderTracked (开发模式):
import { onRenderTracked } from 'vue'
onRenderTracked((event) => {
console.log('跟踪到依赖:', event)
})
onRenderTriggered (开发模式):
import { onRenderTriggered } from 'vue'
onRenderTriggered((event) => {
console.log('触发重新渲染:', event)
})
3、组合式API与Options API生命周期对应关系
组合式API | Options API |
---|---|
onBeforeMount | beforeMount |
onMounted | mounted |
onBeforeUpdate | beforeUpdate |
onUpdated | updated |
onBeforeUnmount | beforeUnmount |
onUnmounted | unmounted |
onErrorCaptured | errorCaptured |
onActivated | activated |
onDeactivated | deactivated |
4、实际应用示例
<template>
<div>
<h2>生命周期示例</h2>
<p>计数: {{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
const count = ref(0)
onBeforeMount(() => {
console.log('1. 组件即将挂载 - count:', count.value)
})
onMounted(() => {
console.log('2. 组件已挂载 - 可以访问DOM')
// 这里可以执行需要DOM的操作
})
onBeforeUpdate(() => {
console.log('3. 组件即将更新 - 旧count:', count.value)
})
onUpdated(() => {
console.log('4. 组件已更新 - 新count:', count.value)
})
onBeforeUnmount(() => {
console.log('5. 组件即将卸载')
})
onUnmounted(() => {
console.log('6. 组件已卸载')
// 清理工作
})
</script>
5、最佳实践
- 逻辑组织:将相关生命周期逻辑放在一起,保持代码可读性;
- 清理工作:在onUnmounted中清理定时器、事件监听等资源;
- 避免滥用:不是所有逻辑都需要放在生命周期中,有些可以放在方法中按需调用;
- 异步操作:在onMounted中进行DOM操作或数据获取;
- 性能优化:使用onRenderTracked和onRenderTriggered调试性能问题;
九、组合式API - 父子通信
1、父传子 - Props
父组件传递props:
<!-- ParentComponent.vue -->
<template>
<ChildComponent
:title="pageTitle"
:count="counter"
:user="userData"
/>
</template>
<script setup>
import { ref, reactive } from 'vue'
import ChildComponent from './ChildComponent.vue'
const pageTitle = ref('Vue 3父子通信')
const counter = ref(0)
const userData = reactive({
name: '张三',
age: 25
})
</script>
子组件接收props:
<!-- ChildComponent.vue -->
<template>
<div>
<h2>{{ title }}</h2>
<p>计数: {{ count }}</p>
<p>用户: {{ user.name }} - {{ user.age }}岁</p>
</div>
</template>
<script setup>
// 定义props
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
},
user: {
type: Object,
default: () => ({})
}
})
</script>
2、子传父 - Emits
子组件触发事件:
<!-- ChildComponent.vue -->
<template>
<button @click="increment">增加计数</button>
<button @click="sendData">发送数据</button>
</template>
<script setup>
// 定义emits
const emit = defineEmits(['increment', 'submit-data'])
const increment = () => {
emit('increment')
}
const sendData = () => {
emit('submit-data', {
message: '来自子组件的数据',
timestamp: new Date()
})
}
</script>
父组件监听事件:
<!-- ParentComponent.vue -->
<template>
<ChildComponent
@increment="handleIncrement"
@submit-data="handleData"
/>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
const handleIncrement = () => {
console.log('子组件触发了increment事件')
}
const handleData = (data) => {
console.log('接收到子组件数据:', data)
}
</script>
3、使用v-model实现双向绑定
Vue 3支持多个v-model绑定:
子组件:
<!-- ChildComponent.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
父组件:
<!-- ParentComponent.vue -->
<template>
<ChildComponent v-model="message" />
<!-- 相当于 -->
<!-- <ChildComponent :modelValue="message" @update:modelValue="message" /> -->
<p>父组件中的消息: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const message = ref('')
</script>
4、使用插槽(Slot)通信
默认插槽:
<!-- ParentComponent.vue -->
<template>
<ChildComponent>
<p>这是插入到子组件的内容</p>
</ChildComponent>
</template>
<!-- ChildComponent.vue -->
<template>
<div>
<h2>子组件标题</h2>
<slot></slot> <!-- 插槽内容将在这里渲染 -->
</div>
</template>
具名插槽:
<!-- ParentComponent.vue -->
<template>
<ChildComponent>
<template #header>
<h1>自定义标题</h1>
</template>
<template #footer>
<p>自定义页脚</p>
</template>
</ChildComponent>
</template>
<!-- ChildComponent.vue -->
<template>
<div>
<slot name="header"></slot>
<div>子组件内容</div>
<slot name="footer"></slot>
</div>
</template>
作用域插槽:
<!-- ParentComponent.vue -->
<template>
<ChildComponent v-slot="slotProps">
<p>来自子组件的数据: {{ slotProps.userData }}</p>
</ChildComponent>
</template>
<!-- ChildComponent.vue -->
<template>
<div>
<slot :userData="user"></slot>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: '李四',
age: 30
})
</script>
5、使用provide/inject跨层级通信
祖先组件提供数据:
<!-- AncestorComponent.vue -->
<template>
<ParentComponent />
</template>
<script setup>
import { provide, ref } from 'vue'
import ParentComponent from './ParentComponent.vue'
const theme = ref('dark')
// 提供数据
provide('theme', theme)
provide('appName', 'Vue 3应用')
</script>
后代组件注入数据:
<!-- DescendantComponent.vue -->
<template>
<div :class="theme">
<h2>{{ appName }}</h2>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据
const theme = inject('theme', 'light') // 第二个参数是默认值
const appName = inject('appName')
</script>
还可以传递方法;
祖先组件提供方法:
<!-- AncestorComponent.vue -->
<template>
<ParentComponent />
</template>
<script setup>
import { provide } from 'vue'
import ParentComponent from './ParentComponent.vue'
// 提供方法
const showNotification = (message) => {
alert(`通知: ${message}`)
}
const updateTheme = (newTheme) => {
console.log('主题更新为:', newTheme)
}
provide('showNotification', showNotification)
provide('updateTheme', updateTheme)
</script>
后代组件使用方法:
<!-- DescendantComponent.vue -->
<template>
<button @click="notify">显示通知</button>
<button @click="changeTheme">切换主题</button>
</template>
<script setup>
import { inject } from 'vue'
// 注入方法
const showNotification = inject('showNotification')
const updateTheme = inject('updateTheme')
const notify = () => {
showNotification('这是来自子组件的消息')
}
const changeTheme = () => {
updateTheme('dark')
}
</script>
6、使用ref访问子组件
<!-- ParentComponent.vue -->
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = ref(null)
const callChildMethod = () => {
if (childRef.value) {
childRef.value.sayHello()
}
}
</script>
<!-- ChildComponent.vue -->
<script setup>
const sayHello = () => {
console.log('Hello from child component!')
}
// 暴露方法给父组件
defineExpose({
sayHello
})
</script>
defineExpose:
- 默认情况下在
<script setup>
语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose
编译宏指定哪些属性和方法容许访问;
7、最佳实践
- props向下:数据流应该主要从父组件流向子组件;
- events向上:子组件应该通过事件通知父组件状态变化;
- 避免直接修改props:子组件不应该直接修改props,应该通过emit事件让父组件修改;
- 合理使用provide/inject:适用于跨多层级的通信,但不应该滥用;
- 谨慎使用ref访问子组件:这会增加组件间的耦合度;
- 类型安全:使用TypeScript时,为props和emits定义明确的类型;
十、Vue3.3 新特性
1、defineOptions
Vue 3.3 引入了 defineOptions
宏,这是一个在 <script setup>
中定义组件选项的新方式,解决了之前无法直接在 <script setup>
中声明组件选项(如 name
、inheritAttrs
等)的问题。
<script setup>
import { defineOptions } from 'vue'
defineOptions({
name: 'MyComponent',
inheritAttrs: false
})
</script>
2、defineModel
Vue 3.3 引入了 defineModel
宏,这是一个简化组件双向绑定的新特性,特别针对 v-model
的使用场景进行了优化。
<!-- ChildComponent.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model">
</template>
<!-- ParentComponent.vue -->
<template>
<ChildComponent v-model="message" />
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
十一、Vue3 Pinia的使用
Pinia 是 Vue 3 的官方推荐状态管理库,相比 Vuex 更简单、更符合组合式 API 的设计理念。
1、安装 Pinia
npm install pinia
# 或
yarn add pinia
2、创建 Pinia 实例并注册
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
3、定义 Store
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment()
}
}
})
Pinia 也支持组合式 API 风格的 Store 定义:
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
const name = ref('Eduardo')
// getters (使用 computed)
const doubleCount = computed(() => count.value * 2)
const doubleCountPlusOne = computed(() => doubleCount.value + 1)
// actions (普通函数)
function increment() {
count.value++
}
async function incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
increment()
}
return {
// state
count,name,
// getters
doubleCount,doubleCountPlusOne,
// actions
increment,incrementAsync
}
})
4、在组件中使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 直接访问 state
console.log(counter.count) // 0
// 调用 action
counter.increment()
// 使用 getter
console.log(counter.doubleCount) // 2
</script>
<template>
<div>
<h1>{{ counter.count }}</h1>
<button @click="counter.increment()">Increment</button>
<p>Double count: {{ counter.doubleCount }}</p>
</div>
</template>
5、状态持久化
可以使用 pinia-plugin-persistedstate 插件实现状态持久化:
npm install pinia-plugin-persistedstate
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
然后在 Store 中启用:
export const useCounterStore = defineStore('counter', {
// ...其他配置
persist: true
})
在选项式 API 中使用:
import { mapState, mapActions } from 'pinia'
import { useCounterStore } from '@/stores/counter'
export default {
computed: {
...mapState(useCounterStore, ['count', 'doubleCount'])
},
methods: {
...mapActions(useCounterStore, ['increment'])
}
}
在组合式API中使用:
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 直接访问 state 和 getters
const count = computed(() => counter.count)
const doubleCount = computed(() => counter.doubleCount)
// 直接调用 actions
function increment() {
counter.increment()
}
</script>
<template>
<!-- 模板中使用方式不变 -->
<div>{{ count }}</div>
<div>{{ doubleCount }}</div>
<button @click="increment">Increment</button>
</template>
6、Pinia 的主要优势
- 更简单的 API 设计;
- 完美的 TypeScript 支持;
- 支持组合式 API;
- 模块化设计,无需嵌套模块;
- 更轻量级;