學習參考視頻:尚硅谷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 | 需要解構多個或所有屬性時 |