Vue3 常見 API#
reactive# Vue Guide#
數據屬性 Data#
組件的 data
選項是一個函數。Vue 在創建新組件實例的過程中調用此函數。它應該返回一個對象,然後 Vue 會通過響應性系統將其包裹起來,並以 $data
的形式存儲在組件實例中。為方便起見,該對象的任何頂級 property 也直接通過組件實例暴露出來:
- 本質上,組件的
data
選項只是一個函數 - 這個函數會在 Vue 創建組件實例時被調用
- 這個函數會返回一個對象
- 返回的這個對象會被 Vue 包裝成響應式數據
- 包裝後的響應式數據,以
$data
的形式存儲在組件實例中 - 為方便,
data
返回的對象的任何頂級屬性可以直接通過組件實例直接拿到,前提是需要確保它們都在data
函數返回的對象中 - 如果直接在組件實例上添加新屬性,雖然可以添加成功,但是由於它沒有在響應式對象
$data
中,所以該屬性不具有響應式 - 在
data
返回的對象的屬性,應避免使用$
和_
開頭
原理分析#
為什麼
data
屬性是一個函數而不是一個對象?
- 結論:
data
定義可是函數也可以是對象,但是根組件實例化時可以是對象或函數,組件實例化只能是函數 - 原理:根實例是單例,不會產生數據污染;組件實例不是單例,為防止多個組件實例之間共用一個
data
,產生數據污染,採用函數形式,初始化data
時會將其作為工廠函數都會返回全新data
對象
計算屬性 Computed#
應用場景:
- 模版中,較長的表達式會讓模版變得複雜和難以維護,為了解決這個問題
computed
孕育而生 - 具有緩存效果,提示了性能,當依賴數據未發生變化,調用的是緩存的數據
對於任何包含響應式數據的 複雜邏輯,都應該使用 計算屬性
計算屬性和函數方法#
計算屬性是基於它們的反應依賴滾系緩存的。計算屬性只在相關響應式依賴發生改變時它們才會重新求值。
相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。
我們為什麼需要緩存?
假設我們有一個性能開銷比較大的計算屬性 list
,它需要遍歷一個巨大的數組並做大量的計算。然後我們可能有其他的計算屬性依賴於 list
。如果沒有緩存,我們將不可避免的多次執行 list
的 getter
!如果你不希望有緩存,請用 method
來替代。
監聽器 Watch#
Options API 實現 Watch#
普通的 watch#
export default {
props:['dataList'],
data(){
return {
sourceData:[],
total:0
}
},
// 當props的dataList發生變化時,data中的值也會重新被計算並賦值
watch:{
dataList(newVal,oldVal){
this.total = newVal.length
this.sourceData = newVal
}
}
}
對象的 watch#
export default {
data(){
return {
firstName:'Li',
lasteName:'Huan'
}
},
// 當props的dataList發生變化時,data中的值也會重新被計算並賦值
watch:{
dataList:{
// getter
get:function(){
return this.firstName + this.lasteName
},
set:function(val){
// this.dataList = 'Wang Feng'
const [ firstName,lasteName ] = val.spilt(' ')
this.firstName = firstName
this.lasteName = lasteName
},
{
// 對象和數組都是引用類型,引用類型變量存的是地址,地址沒有變
// 所以不會觸發 watch。這時我們需要進行深度監聽,需要加上屬性 deep: true
deep:true,
// watch 有一個特點,當值第一次綁定的時候,不會執行監聽函數,
// 只有值發生變化時才會執行,如果想要最初綁定值的時候也執行函數,需要加 immediate 屬性
immediate:true
}
}
}
}
Reactive API 實現 watch#
watch 函數用來監聽特定的數據源,並在回調函數中執行副作用。默認情況下是 懶性 的,也就是說僅在監聽的源數據變更時才執行回調。
watch(source, callback, [options]);
參數說明:
source
:可以支持 String、Object、Function 和 Array 用於指定要監聽的響應式變量callback
:執行的回調函數options
:支持deep
、immediate
和flush
選項
監聽 reactive#
import { defineComponent,reactive,watch } from 'vue'
export default defineComponent({
setup(){
const state = reactive({
firstName:'Li',
lastName:'Wang'
})
// 當修改 firstName時會自動觸發 watch 的回調該函數
watch(
()=>state.firstName,
(newVal,oldVal)=>{
console.log(`新值:${newVal}`,`舊值:${oldVal}`)
},
{deep:true})
}
})
監聽多個數據#
const stop = watch([() => state.firstName, () => state.lastName], ([curAge, newVal], [preAge, oldVal]) => {
console.log('新值:', curAge, '老值:', preAge);
console.log('新值:', newVal, '老值:', oldVal);
});
unmounted(()=>{
// 停止監聽
stop()
})
watchEffect#
watchEffect
不需要像 watch
那樣需要先傳入依賴,watchEffect
會自動收集依賴,只要指定一個回調函數。在組件初始化時,會先執行一次來收集依賴,然後當收集到的依賴中數據發生變化時,就會再次執行回調函數。
watchEffect(()=>{
// 自動搜集依賴,初始化執行一次
console.log(firstName);
console.log(lastName)
})
樣式設置#
對象語法#
<template>
<div class='static' :class="{active:isActive,danger:hasError}"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
}
}
</script>
<!-- 渲染後 -->
<div class="static active danger"></div>
直接傳入對象#
<template>
<div class='static' :class="classObj"></div>
</template>
<script>
export default {
data(){
return {
classObj:{
active:true,
hasError:true
}
}
}
}
</script>
computed 傳入#
<template>
<div class="static" :class="clsObject"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
},
computed:{
classObj(){
return {
active:this.active,
hasError:this.hasError
}
}
}
}
</script>
條件渲染#
<template>
<div v-if='show'></div>
<div v-else></div>
<div v-show='show'></div>
</template>
<script>
export default {
data(){
return {
show:true
}
}
}
</script>
v-if
vs v-show
:
v-show
只是單純的修改元素的css
屬性,它將元素的disable
設置為了none
v-if
是將元素從組件樹中移除,當元素是一個組件時,修改show
值,會觸發組件元素的生命週期函數
內置組件#
component#
動態組件
// 動態組件
<template>
<!-- currentTab 改變時組件也改變 -->
<component :is="tabs[currentTab]"></component>
</template>
在上面的例子中,被傳給 :is
的值可以是以下幾種:
- 被註冊的組件名
- 導入的組件對象
transition#
動畫組件
<!-- 模板 -->
<template>
<Transition name="silde-fade">
</Transition>
</template>
/*
進入和離開動畫可以使用不同
持續時間和速度曲線。
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
- 過渡鉤子函數
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
</Transition>
</template>
// 在元素被插入到 DOM 之前被調用
// 用這個來設置元素的 "enter-from" 狀態
function onBeforeEnter(el) {}
// 在元素被插入到 DOM 之後的下一幀被調用
// 用這個來開始進入動畫
function onEnter(el, done) {
// 調用回調函數 done 表示過渡結束
// 如果與 CSS 結合使用,則這個回調是可選參數
done()
}
// 當進入過渡完成時調用。
function onAfterEnter(el) {}
function onEnterCancelled(el) {}
// 在 leave 鉤子之前調用
// 大多數時候,你應該只會用到 leave 鉤子
function onBeforeLeave(el) {}
// 在離開過渡開始時調用
// 用這個來開始離開動畫
function onLeave(el, done) {
// 調用回調函數 done 表示過渡結束
// 如果與 CSS 結合使用,則這個回調是可選參數
done()
}
// 在離開過渡完成、
// 且元素已從 DOM 中移除時調用
function onAfterLeave(el) {}
// 僅在 v-show 過渡中可用
function onLeaveCancelled(el) {}
keep-alive#
用於緩存組件狀態,避免組件重複渲染
keep-alive
有以下三個屬性
include
:字符串或正則表達式,只有名稱匹配的組件會被緩存,組件的 name;exclude
:字符串或正則表達式,任何名稱匹配的組件都不會被緩存;max
:數字,最多可以緩存多少組件實例。
slot#
默認插槽
<template>
<!-- 父組件 -->
<Child>
<div>默認插槽</div>
</Child>
<!-- 子組件 -->
<template>
<slot>
<p>插槽後備的內容</p>
</slot>
</template>
</template>
具名插槽#
<!-- 組件內部 -->
<div class="container">
<header>
<!-- 我們希望把頁頭放這裡 -->
</header>
<main>
<!-- 我們希望把主要內容放這裡 -->
</main>
<footer>
<!-- 我們希望把頁腳放這裡 -->
</footer>
</div>
<!-- 組件內部 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
作用域插槽#
父組件在自定義子組件中的插槽內容時,需要獲取到子組件中的組件數據
<template>
<!-- Child -->
<ul>
<li v-for="(item, index) in items">
<slot :item="item"></slot>
</li>
</ul>
<!-- Parent -->
<todo-list>
<template #default="{slotProps}">
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
</template>
Teleport#
teleport 是 Vue3.x 新推出的功能,提供了一種乾淨的方法,允許我們控制在 DOM 中哪個父節點下渲染了 HTML
<template>
<Teleport to="body">
<div> teleport to body </div>
</Teleport>
</template>
import { reactive } from 'vue';
export default {
// `setup` 是一個專門用於組合式API 的特殊鉤子
setup() {
const state = reactive({ count: 0 }); // 暴露 state到模板
return { state };
},
};
ref#
import { ref } from 'vue';
const count = ref(0);
console.log(count); // { value: 0 }
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
computed#
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一個計算屬性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
watch#
<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>
watchEffect#
watchEffect () 會立即執行一遍回調函數,如果這時函數產生了副作用,
Vue 會自動追蹤副作用的依賴關係,自動分析出響應源。
watchEffect(async () => {
const response = await fetch(url.value);
data.value = await response.json();
});
watch vs. watchEffect#
watch
和watchEffect
都能響應式地執行有副作用的回調。它們之間的主要區別是追蹤響應式依賴的方式:watch
只追蹤明確監聽的源。它不會追蹤任何在回調中訪問到的東西。另外,僅在響應源確實改變時才會觸發回調。watch
會避免在發生副作用時追蹤依賴,因此,我們能更加精確地控制回調函數的觸發時機。
watchEffect
,則會在副作用發生期間追蹤依賴。它會在同步執行過程中,自動追蹤所有能訪問到的響應式 property。這更方便,而且代碼往往更簡潔,但其響應性依賴關係不那麼明確。
狀態管理 pinia#
store#
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 };
},
// could also be defined as
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
},
getters(){
finishedTodos(store){
return store.count++
}
}
});
action#
import { useCounterStore } from '@/stores/counter';
export default {
setup() {
const counter = useCounterStore();
counter.count++;
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 });
// or using an action instead
counter.increment();
},
};
vue-router 路由#
router.js
const Home = { template: '<div>Home</div>' };
const About = { template: '<div>About</div>' };
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
});
const app = Vue.createApp({});
app.use(router);
app.mount('#app');
渲染
<template>
<router-view />
</template>
setup 語法#
defineProps defineEmits#
Ts 語法支持
const props = defineProps<{
foo: string
bar?: number
}>()
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
defineExpose#
setup 組件是默認關閉
<script setup>
import {ref} from 'vue' const a = 1 const b = ref(2) defineExpose({(a, b)})
</script>