## `setup`函數是組合式API的入口
> 新的`setup`函數在組件被創建**之前**執行,一旦`props`被解析完成,它就將被作為組合式 API 的入口,`setup`接收兩個參數,分別是`props`和`context`
~~~javascript
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
return {} // 這里返回的任何內容都可以用于組件的其余部分
}
// 組件的“其余部分”
}
~~~
## 在`setup`函數中使用響應式變量和方法
~~~javascript
import { fetchUserRepositories } from '@/api/repositories'
import { ref , reactive , toRefs } from 'vue'
//通過ref和reactive創建響應式變量
setup (props) {
//響應變量,通過repositories.value 去訪問值
const repositories = ref([])
//使用reactive創建響應式數據,reactive的參數必須是一個對象才是響應式數據,讀取值通過data.name
const data = reactive({
name:'張三'
})
//方法
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
//暴露給模版使用
return {
repositories,
data,
getUserRepositories
}
}
~~~
## 在`setup`函數中使用生命周期
在`setup`中通過導入的形式注冊生命周期
~~~javascript
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// 在我們的組件中
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // 在 `mounted` 時調用 `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
~~~
## 在`setup`函數中使用偵聽器和計算屬性
在`setup`中通過導入 `watch`和`computed`使用偵聽器和計算屬性
~~~javascript
//導入watch 偵聽器函數
import { ref, watch } from 'vue'
//watch 第一個參數可以是一個基本類型的值或者一個函數
const counter = ref(0)
//偵聽ref的值
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
const data = reactive({name:'張三'})
//偵聽reactive的值,傳遞一個函數
watch(() => data.name , (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
//使用計算屬性
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
~~~
## 響應式解構`toRefs`和`toRef`
~~~javascript
import { toRefs } from 'vue'
setup(props) {
// 解構props,如果是ES6解構 const { title } = props,外部的title變化,傳進來的title不是最新的值
const { title } = toRefs(props)
console.log(title.value)
const data = reactive({
name:'張三'
})
//解構reactive響應式對象
const { name } = toRefs(data)
//console.log(name.value) 對name的修改是響應式的,data.name 會同步變化
//const { name } = data 這樣解構,數據不是響應式的
}
~~~
如果`title`是可選的 prop,沒有傳`title`時`toRefs`將不會為`title`創建一個 ref 。此時可以使用`toRef`替代它:
~~~javascript
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
~~~
## `setup`函數的兩個參數`props`和`context`
`setup`函數中的第一個參數是`props`。`setup`函數中的`props`是響應式的,當傳入新的 prop 時,它將被更新。如果要解構請使用`toRefs`或者`toRef`
~~~javascript
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
~~~
`setup`函數中的第二個參數是`context`。`context`是一個普通 JavaScript 對象,暴露了其它可能在`setup`中有用的值:
* attrs
* slots
* emit
* expose
~~~javascript
export default {
setup(props, context) {
// Attribute (非響應式對象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非響應式對象,等同于 $slots)
console.log(context.slots)
// 觸發事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函數)
console.log(context.expose)
}
}
~~~
`context`是一個普通的 JavaScript 對象,它不是響應式的,可以安全地對`context`使用 ES6 解構
~~~javascript
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
~~~
>[warning] `attrs`和`slots`是有狀態的對象,它們總是會隨組件本身的更新而更新。避免對它們進行解構,并始終以`attrs.x`或`slots.x`的方式使用屬性。
>
## 在`setup`函數中使用模版引用(`ref`)
由于在setup函數中`this`指向不是組件實例,所以`this`并不能使用,也就無法通過`this.$refs`來獲取指定組件,所以要引用組件需要以下步驟:
1. 定義一個`ref`響應式變量,值為null
2. 把變量名賦值到元素的`ref`屬性中
~~~ xml
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM 元素將在初始渲染后分配給 ref
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
}
</script>
~~~
在vue3中如果想通過模版應用`ref`去調用組合式API組件的方法,那么該組件必須使用`expose`顯性的暴露出組件可訪問的方法或變量。
~~~
<script>
import { ref , toRef } from "vue";
export default {
emits: ["myEvent"],
props: {
msg: String,
},
setup(props, { emit ,expose }) {
const msg = toRef(props,'msg');
let count = ref(0);
const onClick = () => {
count.value++;
emit("myEvent");
};
//暴露出屬性或方法,在ref引用時才可以訪問到
expose({
onClick,
msg
})
return {
count,
onClick,
};
},
};
~~~
## 在`setup`函數中使用 `Provide / Inject`
### 父組件使用 Provide
在`setup()`中使用`provide`時,我們首先從`vue`顯式導入`provide`方法。
~~~ xml
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
//需要導入provide使用
import { provide } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup() {
//提供給子組件訪問的屬性或方法
let name = '柴柴老師' // 使用provide配置項注入數據 key - value
provide('name', name)
provide('location', 'North Pole')
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
~~~
### 子組件使用 inject
~~~ xml
<!-- src/components/MyMarker.vue -->
<script>
//需要導入inject使用
import { inject } from 'vue'
export default {
setup() {
//inject 第一個參數是接收的provide名,第二個參數是默認值
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
~~~
### 依賴注入的響應性
`provide`默認情況下傳遞的數據不是響應式的,即祖先組件修改 `provide`數據,子組件接收的并不會響應式的變化,如果想要傳遞響應數據也非常簡單,只需要將傳遞的數據使用ref或者reactive生成即可
~~~xml
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup() {
//在祖先組件修改provide數據,使用inject接收的子組件數據會相應的變化
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
provide('location', location)
provide('geolocation', geolocation)
}
}
</script>
~~~
## 在`setup`中訪問路由和當前路由
~~~
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
},
}
~~~