安裝
安裝Vue3,初始化npm:
npm init vue@3
cd dirname
npm i
npm run dev
一路回車即可帆锋。
修改App.vue:
<script>
export default {
data() {
return {
message: 'LuckyStar'
}
}
}
</script>
<template>
<h1>Hello {{ message }}</h1>
</template>
選項式API
-
模板引用
特別的顷啼,如果一定要操作某個DOM,可以使用 模板引用
:
<script>
export default {
mounted() {
this.$refs.p.textContent = "hello world!"
}
}
</script>
<template>
<p ref="p">hello</p>
</template>
-
Vue3 生命周期
生命周期如下嫡秕。多了一個 setup() 函數(shù)
組合式API(Composition API)
-
setup() 函數(shù)
如上圖所示,setup() 函數(shù)比 beforeCreate() 更早被調(diào)用苹威。
1. Props 參數(shù)
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
2. Context 參數(shù)
export default {
setup(props, context) {
// 透傳 Attributes(非響應(yīng)式的對象昆咽,等價于 $attrs)
console.log(context.attrs)
// 插槽(非響應(yīng)式的對象,等價于 $slots)
console.log(context.slots)
// 觸發(fā)事件(函數(shù)牙甫,等價于 $emit)
console.log(context.emit)
// 暴露公共屬性(函數(shù))
console.log(context.expose)
}
}
context 是非響應(yīng)式的掷酗,可以安全解構(gòu)。
2.1 expose 暴露公共屬性
export default {
setup(props, { expose }) {
// 讓組件實例處于 “關(guān)閉狀態(tài)”
// 即不向父組件暴露任何東西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有選擇地暴露局部狀態(tài)
expose({ count: publicCount })
}
}
3. 返回渲染函數(shù)
setup 也可以返回一個渲染函數(shù)窟哺,此時在渲染函數(shù)中可以直接使用在同一作用域下聲明的響應(yīng)式狀態(tài):
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
返回一個渲染函數(shù)將會阻止我們返回其他東西泻轰。對于組件內(nèi)部來說,這樣沒有問題且轨,但如果我們想通過模板引用將這個組件的方法暴露給父組件浮声,那就有問題了。
我們可以通過調(diào)用 expose() 解決這個問題:
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
-
<script setup>
<script setup> 是在單文件組件 (SFC) 中使用組合式 API 的編譯時語法糖。當(dāng)同時使用 SFC 與組合式 API 時該語法是默認(rèn)推薦。相比于普通的 <script> 語法竭望,它具有更多優(yōu)勢:
- 更少的樣板內(nèi)容贪磺,更簡潔的代碼。
- 能夠使用純 TypeScript 聲明 props 和自定義事件。
- 更好的運行時性能 (其模板會被編譯成同一作用域內(nèi)的渲染函數(shù),避免了渲染上下文代理對象)。
- 更好的 IDE 類型推導(dǎo)性能 (減少了語言服務(wù)器從代碼中抽取類型的工作)矗钟。
<script setup>
console.log('hello script setup')
</script>
1. 頂層的綁定會被暴露給模板
當(dāng)使用 <script setup> 的時候,任何在 <script setup> 聲明的頂層的綁定 (包括變量嫌变,函數(shù)聲明真仲,以及 import 導(dǎo)入的內(nèi)容) 都能在模板中直接使用:
<script setup>
// 變量
const msg = 'Hello!'
// 函數(shù)
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>
import 導(dǎo)入的內(nèi)容也會以同樣的方式暴露。這意味著我們可以在模板表達(dá)式中直接使用導(dǎo)入的 helper 函數(shù)初澎,而不需要通過 methods 選項來暴露它:
<script setup>
import { capitalize } from './helpers'
</script>
<template>
<div>{{ capitalize('hello') }}</div>
</template>
2. 響應(yīng)式
響應(yīng)式狀態(tài)需要明確使用 響應(yīng)式 API 來創(chuàng)建秸应。和 setup()
函數(shù)的返回值一樣虑凛,ref 在模板中使用的時候會自動解包:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
3. 使用 Component
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
4. 動態(tài) Component
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
Vue 響應(yīng)式基礎(chǔ)
https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html
Vue 響應(yīng)式的本質(zhì),實際上是一個 JavaScript Proxy软啼,其行為表現(xiàn)與一般對象相似桑谍。不同之處在于 Vue 能夠跟蹤對響應(yīng)式對象屬性的訪問與更改操作。
要在組件模板中使用響應(yīng)式狀態(tài)祸挪,需要在 setup() 函數(shù)中定義并返回锣披。
import { reactive } from 'vue'
export default {
// `setup` 是一個專門用于組合式 API 的特殊鉤子函數(shù)
setup() {
const state = reactive({ count: 0 })
// 暴露 state 到模板
return {
state
}
}
}
<div>{{ state.count }}</div>
自然,我們也可以在同一個作用域下定義一個更新響應(yīng)式狀態(tài)的函數(shù)贿条,并作為一個方法與狀態(tài)一起暴露出去:
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// 不要忘記同時暴露 increment 函數(shù)
return {
state,
increment
}
}
}
<button @click="increment">
{{ state.count }}
</button>
<script setup> 簡化代碼
在 setup() 函數(shù)中手動暴露大量的狀態(tài)和方法非常繁瑣雹仿。幸運的是,我們可以通過使用構(gòu)建工具來簡化該操作整以。當(dāng)使用單文件組件(SFC)時胧辽,我們可以使用 <script setup> 來大幅度地簡化代碼。
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
nextTick()
當(dāng)你更改響應(yīng)式狀態(tài)后公黑,DOM 會自動更新邑商。然而,你得注意 DOM 的更新并不是同步的凡蚜。相反人断,Vue 將緩沖它們直到更新周期的 “下個時機” 以確保無論你進行了多少次狀態(tài)更改,每個組件都只需要更新一次朝蜘。
若要等待一個狀態(tài)改變后的 DOM 更新完成恶迈,你可以使用 nextTick() 這個全局 API:
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 訪問更新后的 DOM
})
}
深層響應(yīng)性
在 Vue 中,狀態(tài)都是默認(rèn)深層響應(yīng)式的谱醇。這意味著即使在更改深層次的對象或數(shù)組暇仲,你的改動也能被檢測到。
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都會按照期望工作
obj.nested.count++
obj.arr.push('baz')
}
當(dāng)然也可以直接創(chuàng)建一個淺層響應(yīng)式對象枣抱。
響應(yīng)式代理 vs. 原始對象
值得注意的是熔吗,reactive()
返回的是一個原始對象的 Proxy辆床,它和原始對象是不相等的:
const raw = {}
const proxy = reactive(raw)
// 代理對象和原始對象不是全等的
console.log(proxy === raw) // false
只有代理對象是響應(yīng)式的佳晶,更改原始對象不會觸發(fā)更新。因此讼载,使用 Vue 的響應(yīng)式系統(tǒng)的最佳實踐是 僅使用你聲明對象的代理版本轿秧。
為保證訪問代理的一致性,對同一個原始對象調(diào)用 reactive() 會總是返回同樣的代理對象咨堤,而對一個已存在的代理對象調(diào)用 reactive() 會返回其本身:
// 在同一個對象上調(diào)用 reactive() 會返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一個代理上調(diào)用 reactive() 會返回它自己
console.log(reactive(proxy) === proxy) // true
reactive() 的局限性
reactive()
API 有兩條限制:
僅對對象類型有效(對象菇篡、數(shù)組和
Map
、Set
這樣的集合類型)一喘,而對string
驱还、number
和boolean
這樣的 原始類型 無效嗜暴。因為 Vue 的響應(yīng)式系統(tǒng)是通過屬性訪問進行追蹤的,因此我們必須始終保持對該響應(yīng)式對象的相同引用议蟆。這意味著我們不可以隨意地“替換”一個響應(yīng)式對象闷沥,因為這將導(dǎo)致對初始引用的響應(yīng)性連接丟失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 將不再被追蹤(響應(yīng)性連接已丟失!)
state = reactive({ count: 1 })
用 ref() 定義響應(yīng)式變量
reactive()
的種種限制歸根結(jié)底是因為 JavaScript 沒有可以作用于所有值類型的 “引用” 機制咐容。為此舆逃,Vue 提供了一個 ref()
方法來允許我們創(chuàng)建可以使用任何值類型的響應(yīng)式 ref:
import { ref } from 'vue'
const count = ref(0)
ref() 函數(shù)將傳入?yún)?shù)的值包裝為一個帶 .value 屬性的 ref 對象:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
和響應(yīng)式對象的屬性類似,ref 的 .value 屬性也是響應(yīng)式的戳粒。同時路狮,當(dāng)值為對象類型時,會用 reactive() 自動轉(zhuǎn)換它的 .value蔚约。
一個包含對象類型值的 ref 可以響應(yīng)式地替換整個對象:
const objectRef = ref({ count: 0 })
// 這是響應(yīng)式的替換
objectRef.value = { count: 1 }
Vue3 生命周期
每個 Vue 組件實例在創(chuàng)建時都需要經(jīng)歷一系列的初始化步驟奄妨,比如設(shè)置好數(shù)據(jù)偵聽,編譯模板炊琉,掛載實例到 DOM展蒂,以及在數(shù)據(jù)改變時更新 DOM。在此過程中苔咪,它也會運行被稱為生命周期鉤子的函數(shù)锰悼,讓開發(fā)者有機會在特定階段運行自己的代碼。
注冊周期鉤子
舉例來說团赏,onMounted 鉤子可以用來在組件完成初始渲染并創(chuàng)建 DOM 節(jié)點后運行代碼:
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
偵聽器 Watch
在組合式 API 中箕般,我們可以使用 watch
函數(shù)在每次響應(yīng)式狀態(tài)發(fā)生變化時觸發(fā)回調(diào)函數(shù):
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
// 可以直接偵聽一個 ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
watch 數(shù)據(jù)源類型
watch 的第一個參數(shù)可以是不同形式的“數(shù)據(jù)源”:它可以是一個 ref (包括計算屬性)、一個響應(yīng)式對象舔清、一個 getter 函數(shù)丝里、或多個數(shù)據(jù)源組成的數(shù)組:
const x = ref(0)
const y = ref(0)
// 單個 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函數(shù)
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多個來源組成的數(shù)組
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
注意,你不能直接偵聽響應(yīng)式對象的屬性值体谒,例如:
const obj = reactive({ count: 0 })
// 錯誤杯聚,因為 watch() 得到的參數(shù)是一個 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
這里需要用一個返回該屬性的 getter 函數(shù):
// 提供一個 getter 函數(shù)
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
深層偵聽器
直接給 watch() 傳入一個響應(yīng)式對象,會隱式地創(chuàng)建一個深層偵聽器——該回調(diào)函數(shù)在所有嵌套的變更時都會被觸發(fā):
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的屬性變更時觸發(fā)
// 注意:`newValue` 此處和 `oldValue` 是相等的
// 因為它們是同一個對象抒痒!
})
obj.count++
而 一個返回響應(yīng)式對象的 getter 函數(shù)幌绍,只有在返回不同的對象時,才會觸發(fā)回調(diào):
watch(
() => state.someObject,
() => {
// 僅當(dāng) state.someObject 被替換時觸發(fā)
}
)
你也可以給上面這個例子顯式地加上 deep 選項故响,強制轉(zhuǎn)成深層偵聽器:
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此處和 `oldValue` 是相等的
// *除非* state.someObject 被整個替換了
},
{ deep: true }
)
watchEffect()
watch() 是懶執(zhí)行的:僅當(dāng)數(shù)據(jù)源變化時傀广,才會執(zhí)行回調(diào)。但在某些場景中彩届,我們希望在創(chuàng)建偵聽器時伪冰,立即執(zhí)行一遍回調(diào)。舉例來說樟蠕,我們想請求一些初始數(shù)據(jù)贮聂,然后在相關(guān)狀態(tài)更改時重新請求數(shù)據(jù)靠柑。我們可以這樣寫:
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// 立即獲取
fetchData()
// ...再偵聽 url 變化
watch(url, fetchData)
我們可以用 watchEffect
來簡化上面的代碼。watchEffect()
會立即執(zhí)行一遍回調(diào)函數(shù)吓懈,如果這時函數(shù)產(chǎn)生了副作用病往,Vue 會自動追蹤副作用的依賴關(guān)系,自動分析出響應(yīng)源骄瓣。上面的例子可以重寫為:
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
這個例子中停巷,回調(diào)會立即執(zhí)行。在執(zhí)行期間榕栏,它會自動追蹤 url.value 作為依賴(和計算屬性的行為類似)畔勤。每當(dāng) url.value 變化時,回調(diào)會再次執(zhí)行扒磁。
watch vs. watchEffect
watch 和 watchEffect 都能響應(yīng)式地執(zhí)行有副作用的回調(diào)庆揪。它們之間的主要區(qū)別是追蹤響應(yīng)式依賴的方式:
- watch 只追蹤明確偵聽的數(shù)據(jù)源。它不會追蹤任何在回調(diào)中訪問到的東西妨托。另外缸榛,僅在數(shù)據(jù)源確實改變時才會觸發(fā)回調(diào)。watch 會避免在發(fā)生副作用時追蹤依賴兰伤,因此内颗,我們能更加精確地控制回調(diào)函數(shù)的觸發(fā)時機。
- watchEffect敦腔,則會在副作用發(fā)生期間追蹤依賴均澳。它會在同步執(zhí)行過程中,自動追蹤所有能訪問到的響應(yīng)式屬性符衔。這更方便找前,而且代碼往往更簡潔,但有時其響應(yīng)性依賴關(guān)系會不那么明確判族。
<Transition> & <TransitionGroup>
Vue 提供了兩個內(nèi)置組件躺盛,可以幫助你制作基于狀態(tài)變化的過渡和動畫:
<Transition>
會在一個元素或組件進入和離開 DOM 時應(yīng)用動畫。本章節(jié)會介紹如何使用它形帮。<TransitionGroup>
會在一個v-for
列表中的元素或組件被插入槽惫,移動,或移除時應(yīng)用動畫沃缘。
<Transition> 組件
該組件可以將進入和離開動畫應(yīng)用到通過默認(rèn)插槽傳遞給它的元素或組件上躯枢。進入或離開可以由以下的條件之一觸發(fā):
- 由 v-if 所觸發(fā)的切換
- 由 v-show 所觸發(fā)的切換
- 由特殊元素 <component> 切換的動態(tài)組件
<Transition> 僅支持單個元素或者組件作為其插槽內(nèi)容则吟。如果內(nèi)容是組件槐臀,則組件必須僅有一個根元素。
基礎(chǔ)用法
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
/* 下面我們會解釋這些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
CSS 過渡效果 class
v-enter-from:進入動畫的起始狀態(tài)氓仲。在元素插入之前添加水慨,在元素插入完成后的下一幀移除得糜。
v-enter-active:進入動畫的生效狀態(tài)。應(yīng)用于整個進入動畫階段晰洒。在元素被插入之前添加朝抖,在過渡或動畫完成之后移除。這個 class 可以被用來定義進入動畫的持續(xù)時間谍珊、延遲與速度曲線類型治宣。
v-enter-to:進入動畫的結(jié)束狀態(tài)。在元素插入完成后的下一幀被添加 (也就是 v-enter-from 被移除的同時)砌滞,在過渡或動畫完成之后移除侮邀。
v-leave-from:離開動畫的起始狀態(tài)。在離開過渡效果被觸發(fā)時立即添加贝润,在一幀后被移除绊茧。
v-leave-active:離開動畫的生效狀態(tài)。應(yīng)用于整個離開動畫階段打掘。在離開過渡效果被觸發(fā)時立即添加华畏,在過渡或動畫完成之后移除。這個 class 可以被用來定義離開動畫的持續(xù)時間尊蚁、延遲與速度曲線類型亡笑。
v-leave-to:離開動畫的結(jié)束狀態(tài)。在一個離開動畫被觸發(fā)后的下一幀被添加 (也就是 v-leave-from 被移除的同時)横朋,在過渡或動畫完成之后移除况芒。
v-enter-active 和 v-leave-active 給我們提供了為進入和離開動畫指定不同速度曲線的能力,我們將在下面的小節(jié)中看到一個示例叶撒。
命名 Transition
<Transition name="fade">
...
</Transition>
對于一個有名字的過渡效果绝骚,對它起作用的過渡 class 會以其名字而不是 v 作為前綴。比如祠够,上方例子中被應(yīng)用的 class 將會是 fade-enter-active 而不是 v-enter-active压汪。這個“fade”過渡的 class 應(yīng)該是這樣:
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}