## `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,
},
})
}
},
}
~~~