### 一、 Composition API的簡單介紹
Composition API也叫組合式API,是Vue3.x的新特性。
通過創建 Vue 組件,我們可以將接口的可重復部分及其功能提取到可重用的代碼段中。僅此一項就可以使我們的應用程序在可維護性和靈活性方面走得更遠。然而,我們的經驗已經證明,光靠這一點可能是不夠的,尤其是當你的應用程序變得非常大的時候——想想幾百個組件。在處理如此大的應用程序時,共享和重用代碼變得尤為重要。
**通俗的講:**
沒有Composition API之前vue相關業務的代碼需要配置到option的特定的區域,中小型項目是沒有問題的,但是在大型項目中會導致后期的維護性比較復雜,同時代碼可復用性不高。Vue3.x中的composition-api就是為了解決這個問題而生的。
**compositon-api提供了以下幾個函數:**
* setup
* ref
* reactive
* watchEffect
* watch
* computed
* toRefs
* 生命周期的hooks
### 二、`setup`組件選項
新的 `setup` 組件選項在**創建組件之前**執行,一旦 `props` 被解析,并充當合成 API 的入口點。
**提示:**
~~~
由于在執行 setup 時尚未創建組件實例,因此在 setup 選項中沒有 this。這意味著,除了props 之外,你將無法訪問組件中聲明的任何屬性——本地狀態、計算屬性或方法。
~~~
使用 `setup` 函數時,它將接受兩個參數:
1. `props`
2. `context`
讓我們更深入地研究如何使用每個參數。
#### 2.1、Props
`setup` 函數中的第一個參數是 `props`。正如在一個標準組件中所期望的那樣,`setup` 函數中的 `props` 是響應式的,當傳入新的 prop 時,它將被更新。
~~~
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
~~~
**注意:**
~~~
但是,因為 props 是響應式的,你不能使用 ES6 解構,因為它會消除 prop 的響應性。
~~~
如果需要解構 prop,可以通過使用 `setup` 函數中的 [`toRefs`](https://v3.cn.vuejs.org/guide/reactivity-fundamentals.html#%E5%93%8D%E5%BA%94%E5%BC%8F%E7%8A%B6%E6%80%81%E8%A7%A3%E6%9E%84) 來安全地完成此操作。
~~~
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
~~~
#### 2.2、上下文
傳遞給 `setup` 函數的第二個參數是 `context`。`context` 是一個普通的 JavaScript 對象,它暴露三個組件的 property:
~~~
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非響應式對象)
console.log(context.attrs)
// 插槽 (非響應式對象)
console.log(context.slots)
// 觸發事件 (方法)
console.log(context.emit)
}
}
~~~
`context` 是一個普通的 JavaScript 對象,也就是說,它不是響應式的,這意味著你可以安全地對 `context` 使用 ES6 解構。
~~~
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
~~~
`attrs` 和 `slots` 是有狀態的對象,它們總是會隨組件本身的更新而更新。這意味著你應該避免對它們進行解構,并始終以 `attrs.x` 或 `slots.x` 的方式引用 property。請注意,與 `props` 不同,`attrs` 和 `slots` 是**非**響應式的。如果你打算根據 `attrs` 或 `slots` 更改應用副作用,那么應該在 `onUpdated` 生命周期鉤子中執行此操作。
#### 2.3、setup組件的 property
執行 `setup` 時,組件實例尚未被創建。因此,你只能訪問以下 property:
* `props`
* `attrs`
* `slots`
* `emit`
換句話說,你**將無法訪問**以下組件選項:
* `data`
* `computed`
* `methods`
#### 2.4、ref reactive 以及setup結合模板使用
在看setup結合模板使用之前,我們首先得知道ref 和 reactive 方法。
如果 `setup` 返回一個對象則可以在模板中綁定對象中的屬性和方法,但是要定義響應式數據的時候可以使用ref, reactive方法定義響應式的數據。
##### **錯誤寫法:**
~~~
<template>
{{msg}}
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
</template>
<script>
export default {
data() {
return {
}
},
setup() {
let msg = "這是setup中的msg";
let updateMsg = () => {
alert("觸發方法")
msg = "改變后的值"
}
return {
msg,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
~~~
##### **正確寫法一:**
**ref**用來定義響應式的 字符串、 數值、 數組、Bool類型
~~~
import {
ref
} from 'vue'
~~~
~~~
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變etup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
</template>
<script>
import {
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let list = ref(["馬總", "李總", "劉總"])
let updateMsg = () => {
alert("觸發方法");
msg.value = "改變后的值"
}
return {
msg,
list,
updateMsg
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
~~~
##### **正確寫法二:**
**reactive** 用來定義響應式的對象
~~~
import {
reactive
} from 'vue'
~~~
~~~
<template>
{{msg}}
<br>
<br>
<button @click="updateMsg">改變setup中的msg</button>
<br>
<br>
<ul>
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<br>
{{setupData.title}}
<br>
<button @click="updateTitle">更新setup中的title</button>
<br>
<br>
</template>
<script>
import {
reactive,
ref
} from 'vue'
export default {
data() {
return {
}
},
setup() {
let msg = ref("這是setup中的msg");
let setupData = reactive({
title: "reactive定義響應式數據的title",
userinfo: {
username: "張三",
age: 20
}
})
let updateMsg = () => {
alert("觸發方法");
msg.value = "改變后的值"
}
let updateTitle = () => {
alert("觸發方法");
setupData.title = "我是改變后的title"
}
return {
msg,
setupData,
updateMsg,
updateTitle
}
},
}
</script>
<style lang="scss">
.home {
position: relative;
}
</style>
~~~
\*\*說明:\*\*要改變ref定義的屬性名稱需要通過 `屬性名稱.value`來修改,要改變reactive中定義的對象名稱可以直接
#### 2.5、使用`this`
**在 `setup()` 內部,`this` 不會是該活躍實例的引用**,因為 `setup()` 是在解析其它組件選項之前被調用的,所以 `setup()` 內部的 `this` 的行為與其它選項中的 `this` 完全不同。這在和其它選項式 API 一起使用 `setup()` 時可能會導致混淆。
### 二、 toRefs - 解構響應式對象數據
把一個響應式對象轉換成普通對象,該普通對象的每個 property 都是一個 ref ,和響應式對象 property 一一對應。
~~~
<template>
<div>
<h1>解構響應式對象數據</h1>
<p>Username: {{username}}</p>
<p>Age: {{age}}</p>
</div>
</template>
<script>
import {
reactive,
toRefs
} from "vue";
export default {
name: "解構響應式對象數據",
setup() {
const user = reactive({
username: "張三",
age: 10000,
});
return {
...toRefs(user)
};
},
};
</script>
~~~
當想要從一個組合邏輯函數中返回響應式對象時,用 toRefs 是很有效的,該 API 讓消費組件可以 解構 / 擴展(使用 … 操作符)返回的對象,并不會丟失響應性:
~~~
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2,
})
// 對 state 的邏輯操作
// ....
// 返回時將屬性都轉為 ref
return toRefs(state)
}
export default {
setup() {
// 可以解構,不會丟失響應性
const { foo, bar } = useFeatureX()
return {
foo,
bar,
}
},
}
~~~
### 三、computed - 計算屬性
~~~
<template>
<div>
<h1>解構響應式對象數據+computed</h1>
<input type="text" v-model="firstName" placeholder="firstName" />
<br>
<br>
<input type="text" v-model="lastName" placeholder="lastName" />
<br>
{{fullName}}
</div>
</template>
<script>
import {
reactive,
toRefs,
computed
} from "vue";
export default {
name: "解構響應式對象數據",
setup() {
const user = reactive({
firstName: "",
lastName: "",
});
const fullName = computed(() => {
return user.firstName + " " + user.lastName
})
return {
...toRefs(user),
fullName
};
},
};
</script>
~~~
### 四、readonly “深層”的只讀代理
傳入一個對象(響應式或普通)或 ref,返回一個原始對象的只讀代理。一個只讀的代理是“深層的”,對象內部任何嵌套的屬性也都是只讀的。
~~~
<template>
<div>
<h1>readonly - “深層”的只讀代理</h1>
<p>original.count: {{original.count}}</p>
<p>copy.count: {{copy.count}}</p>
</div>
</template>
<script>
import { reactive, readonly } from "vue";
export default {
name: "Readonly",
setup() {
const original = reactive({ count: 0 });
const copy = readonly(original);
setInterval(() => {
original.count++;
copy.count++; // 報警告,Set operation on key "count" failed: target is readonly. Proxy {count: 1}
}, 1000);
return { original, copy };
},
};
</script>
~~~
### 五、watchEffect
在響應式地跟蹤其依賴項時立即運行一個函數,并在更改依賴項時重新運行它。
~~~
<template>
<div>
<h1>watchEffect - 偵聽器</h1>
<p>{{data.count}}</p>
<button @click="stop">手動關閉偵聽器</button>
</div>
</template>
<script>
import {
reactive,
watchEffect
} from "vue";
export default {
name: "WatchEffect",
setup() {
const data = reactive({
count: 1,
num: 1
});
const stop = watchEffect(() => console.log(`偵聽器:${data.count}`));
setInterval(() => {
data.count++;
}, 1000);
return {
data,
stop
};
},
};
</script>
~~~
### 六、watch 、watch 與watchEffect區別
對比`watchEffect`,`watch`允許我們:
* 懶執行,也就是說僅在偵聽的源變更時才執行回調;
* 更明確哪些狀態的改變會觸發偵聽器重新運行;
* 訪問偵聽狀態變化前后的值
**更明確哪些狀態的改變會觸發偵聽器重新運行;**
~~~
<template>
<div>
<h1>watch - 偵聽器</h1>
<p>count1: {{data.count1}}</p>
<p>count2: {{data.count2}}</p>
<button @click="stopAll">Stop All</button>
</div>
</template>
<script>
import {
reactive,
watch
} from "vue";
export default {
name: "Watch",
setup() {
const data = reactive({
count1: 0,
count2: 0
});
// 偵聽單個數據源
const stop1 = watch(data, () =>
console.log("watch1", data.count1, data.count2)
);
// 偵聽多個數據源
const stop2 = watch([data], () => {
console.log("watch2", data.count1, data.count2);
});
setInterval(() => {
data.count1++;
}, 1000);
return {
data,
stopAll: () => {
stop1();
stop2();
},
};
},
};
</script>
~~~
**訪問偵聽狀態變化前后的值**
~~~
<template>
<div>
<h1>watch - 偵聽器</h1>
<input type="text" v-model="keywords" />
</div>
</template>
<script>
import {
ref,
watch
} from "vue";
export default {
name: "Watch",
setup() {
let keywords = ref("111");
// 偵聽單個數據源
watch(keywords, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
return {
keywords
};
},
};
</script>
~~~
**懶執行,也就是說僅在偵聽的源變更時才執行回調**
~~~
<template>
<div>
<h1>watch - 偵聽器</h1>
<p>num1={{num1}}</p>
<p>num2={{num2}}</p>
</div>
</template>
<script>
import {
ref,
watch,
watchEffect
} from "vue";
export default {
name: "Watch",
setup() {
let num1 = ref(10);
let num2 = ref(10);
// 偵聽單個數據源
watch(num1, (newValue, oldValue) => {
console.log(newValue, oldValue)
});
watchEffect(() => console.log(`watchEffect偵聽器:${num2.value}`));
return {
num1,
num2
};
},
};
</script>
~~~
### 七、組合式api生命周期鉤子
你可以通過在生命周期鉤子前面加上 “on” 來訪問組件的生命周期鉤子。
下表包含如何在 [setup ()](https://v3.cn.vuejs.org/guide/composition-api-setup.html) 內部調用生命周期鉤子:
| 選項式 API | Hook inside`setup` |
| --- | --- |
| `beforeCreate` | Not needed\* |
| `created` | Not needed\* |
| `beforeMount` | `onBeforeMount` |
| `mounted` | `onMounted` |
| `beforeUpdate` | `onBeforeUpdate` |
| `updated` | `onUpdated` |
| `beforeUnmount` | `onBeforeUnmount` |
| `unmounted` | `onUnmounted` |
| `errorCaptured` | `onErrorCaptured` |
| `renderTracked` | `onRenderTracked` |
| `renderTriggered` | `onRenderTriggered` |
因為 `setup` 是圍繞 `beforeCreate` 和 `created` 生命周期鉤子運行的,所以不需要顯式地定義它們。換句話說,在這些鉤子中編寫的任何代碼都應該直接在 `setup` 函數中編寫。
~~~
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
~~~
### 八、Provider Inject
通常,當我們需要將數據從父組件傳遞到子組件時,我們使用 [props](https://v3.cn.vuejs.org/guide/component-props.html)。想象一下這樣的結構:你有一些深嵌套的組件,而你只需要來自深嵌套子組件中父組件的某些內容。在這種情況下,你仍然需要將 prop 傳遞到整個組件鏈中,這可能會很煩人。
對于這種情況,我們可以使用 `provide` 和 `inject` 對父組件可以作為其所有子組件的依賴項提供程序,而不管組件層次結構有多深。這個特性有兩個部分:父組件有一個 `provide` 選項來提供數據,子組件有一個 `inject` 選項來開始使用這個數據。

#### **8.1 、非組合式api中的寫法:**
~~~
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
~~~
#### **8.2、組合式api中的寫法:**
##### Provider:
在 `setup()` 中使用 `provide` 時,我們首先從 `vue` 顯式導入 `provide` 方法。這使我們能夠調用 `provide` 時來定義每個 property。
`provide` 函數允許你通過兩個參數定義 property:
1. property 的 name (`<String>`類型)
2. property 的 value
使用 `MyMap` 組件,我們提供的值可以按如下方式重構:
~~~
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
~~~
##### Inject:
在 `setup()` 中使用 `inject` 時,還需要從 `vue` 顯式導入它。一旦我們這樣做了,我們就可以調用它來定義如何將它暴露給我們的組件。
`inject` 函數有兩個參數:
1. 要注入的 property 的名稱
2. 一個默認的值 (**可選**)
使用 `MyMarker` 組件,可以使用以下代碼對其進行重構:
~~~
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
~~~
**Provider Inject 響應性**
**父組件:**
~~~
import {
provide,
ref,
reactive
} from 'vue'
setup() {
const location = ref('北京')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = '上海'
}
provide('location', location);
provide('geolocation', geolocation);
return {
updateLocation
}
}
~~~
~~~
<button @click="updateLocation">改變location</button>
~~~
**子組件:**
~~~
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
~~~
- 空白目錄
- 第一節 Vue3.x教程、Vue3.x簡介、搭建Vue3.x環境、創建運行Vue3.x項目、分析Vue目錄結構
- 第二節 Vue3.x綁定數據、綁定html、綁定屬性、循環數據
- 第三節 Vue3.x中的事件方法入門、模板語法模板中類和樣式綁定
- 第四節 Vue3.x中的事件方法詳解、事件監聽、方法傳值、事件對象、多事件處理程序、事件修飾符、按鍵修飾符
- 第五節 Vue3.x中Dom操作$refs 以及表單( input、checkbox、radio、select、 textarea )結合雙休數據綁定實現在線預約功能
- 第六節 Vue3.x中使用JavaScript表達式 、條件判斷、 計算屬性和watch偵聽
- 第七節 Vue3.x 實現一個完整的toDoList(待辦事項) 以及類似京東App搜索緩存數據功能
- 第八節 Vue3.x中的模塊化以及封裝Storage實現todolist 待辦事項 已經完成的持久化
- 第九節 Vue3.x中的單文件組件 定義組件 注冊組件 以及組件的使用
- 第十節 Vue3.x父組件給子組件傳值、Props、Props驗證、單向數據流
- 第十一節 Vue3.x父組件主動獲取子組件的數據和執行子組件方法 、子組件主動獲取父組件的數據和執行父組件方法
- 第十二節 Vue3.x組件自定義事件 以及mitt 實現非父子組件傳值
- 第十三節 Vue3.x自定義組件上面使用v-mode雙休數據綁定 以及 slots以及 Prop 的Attribute 繼承 、禁用 Attribute 繼承
- 第十四節 Vue3.x中組件的生命周期函數(lifecycle)、 this.$nextTick、動態組件 keep-alive、Vue實現Tab切換
- 第十五節 Vue3.x中全局綁定屬性、使用Axios和fetchJsonp請求真實api接口數據、函數防抖實現百度搜索
- 第十六節 Vue3.x中的Mixin實現組件功能的復用 、全局配置Mixin
- 第十七節 Vue3.x Teleport、使用Teleport自定義一個模態對話框的組件
- 第十八節 Vue3.x Composition API 詳解
- 第十九節 Vue3.x中集成Typescript 使用Typescript
- 第二十節 Vue-Router 詳解
- 第二十節 Vuex教程-Vuex 中的 State Mutation Getters mapGetters Actions Modules