一. computed 函数使用
1. computed 方法
- 在前面我们讲解过计算属性
computed
:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理- 在前面的
Options API
中,我们是使用computed
选项来完成的 - 在
Composition API
中,我们可以在setup
函数中使用computed
方法来编写一个计算属性
- 在前面的
- 如何使用
computed
呢?- 方式一:接收一个
getter
函数,并为getter
函数返回的值,返回一个不变的ref
对象 - 方式二:接收一个具有
get
和set
的对象,返回一个可变的(可读写)ref
对象
- 方式一:接收一个
<template>
<h2>{{ fullname }}</h2>
<button @click="setFullname">设置fullname</button>
<h2>{{ scoreLevel }}</h2>
</template>
<script>
import { reactive, computed, ref } from 'vue'
export default {
setup() {
const names = reactive({
firstName: "kobe",
lastName: "bryant"
})
// const fullname = computed(() => {
// return names.firstName + " " + names.lastName
// })
const fullname = computed({
set: function(newValue) {
const tempNames = newValue.split(" ")
names.firstName = tempNames[0]
names.lastName = tempNames[1]
},
get: function() {
return names.firstName + " " + names.lastName
}
})
function setFullname() {
fullname.value = "coder why"
}
const score = ref(89)
const scoreLevel = computed(() => {
return score.value >= 60 ? "及格": "不及格"
})
return {
names,
fullname,
setFullname,
scoreLevel
}
}
}
</script>
2. setup 中使用 ref
在
setup
中如何使用ref
获取元素或者组件?- 只需要定义一个
ref
对象,绑定到元素 或组件的ref
属性上即可
- 只需要定义一个
二. 组件的生命周期函数
我们前面说过
setup
可以用来替代data
、methods
、computed
等这些选项,也可以替代生命周期钩子那么
setup
中如何使用生命周期函数呢?可以使用直接导入的
onXXX
函数注册生命周期钩子setup
中没有beforeCreate
和created
两个生命周期函数实际业务中直接在
setup
中执行业务就行了
从
vue3
的官方文档中可以得知,其实setup
执行开始在beforeCreate
之前示例如下:
vue<template> <div>AppContent</div> </template> <script> import { onMounted, onUpdated, onUnmounted } from 'vue' export default { // beforeCreate() {}, // created() {}, // beforeMount() {}, // mounted() {}, // beforeUpdate() {}, // updated() {} setup() { // 在执行setup函数的过程中, 你需要注册别的生命周期函数 onMounted(() => { console.log("onmounted") }) } } </script>
三. Provide / Inject 使用
1. Provide 函数
- 事实上我们之前还学习过
Provide
和Inject
,Composition API
也可以替代之前的Provide
和Inject
的选项 - 我们可以通过
provide
来提供数据:- 可以通过
provide
方法来定义每个Property
- 可以通过
provide
可以传入两个参数:provide(name, value)
name
:提供的属性名称value
:提供的属性值
2. Inject 函数
- 在后代组件中可以通过
inject
来注入需要的属性和对应的值:- 可以通过
inject
来注入需要的内容
- 可以通过
inject
可以传入两个参数:- 第一个参数:要
inject
的property
的name
- 第二个参数:默认值
- 第一个参数:要
3. 数据的响应式
为了增加
provide
值和inject
值之间的响应性,我们可以在provide
值时使用ref
和reactive
有的时候,我们可能需要在注入方组件中更改数据
在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数:
vue<!-- 在供给方组件内 --> <script setup> import { provide, ref } from 'vue' const location = ref('North Pole') function updateLocation() { location.value = 'South Pole' } provide('location', { location, updateLocation // 将更改数据的方法函数通过provide第二个参数用对象形式带过去 }) </script>
vue<!-- 在注入方组件 --> <template> <button @click="updateLocation">{{ location }}</button> </template> <script setup> import { inject } from 'vue' const { location, updateLocation } = inject('location') </script>
四. watch / watchEffect
1.侦听数据的变化
- 在前面的
Options API
中,我们可以通过watch
选项来侦听data
或者props
的数据变化,当数据变化时执行某一些操作 - 在
Composition API
中,我们可以使用watchEffect
和watch
来完成响应式数据的侦听watchEffect
:用于自动收集响应式数据的依赖watch
:需要手动指定侦听的数据源
2. Watch 的使用
watch
的API
完全等同于组件watch
选项的Property
:watch
需要侦听特定的数据源,并且执行其回调函数- 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调
vue<template> <button @click="message = '你好啊,李银河!'">修改message</button> </template> <script> import { ref, watch } from 'vue' export default { setup() { const message = ref("Hello World") // 侦听 message 数据的变化 watch(message, (newValue, oldValue) => { console.log(newValue, oldValue) }) return { message } } } </script>
3. 侦听多个数据源
侦听器还可以使用数组同时侦听多个源:
jsconst name = ref('later') const age = ref(18) const changeName = () => { name.value = 'hello' } watch([name, age], (newVals, oldVals) => { console.log(newVals, oldVals) })
4. watch 的选项
如果我们希望进行一个深层的侦听,那么依然需要设置
deep
为true
:也可以传入
immediate
立即执行vue<template> <button @click="info.friend.name = 'james'">修改info</button> </template> <script> import { reactive, watch } from 'vue' export default { setup() { const info = reactive({ name: "why", age: 18, friend: { name: "kobe" } }) watch(info, (newValue, oldValue) => { console.log(newValue === oldValue) // true }, { immediate: true }) // 监听reactive数据变化后, 获取普通对象 watch(() => ({ ...info }), (newValue, oldValue) => { console.log(newValue, oldValue) }, { immediate: true, deep: true }) return { info } } } </script>
5. watchEffect
当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用
watchEffect
我们来看一个案例:
- 首先,
watchEffect
传入的函数会被立即执行一次,并且在执行的过程中会收集依赖 - 其次,只有收集的依赖发生变化时,
watchEffect
传入的函数才会再次执行
vue<template> <div> <h2>当前计数: {{ counter }}</h2> <button @click="counter++">+1</button> <button @click="name = 'kobe'">修改name</button> </div> </template> <script> import { watchEffect, watch, ref } from 'vue' export default { setup() { const counter = ref(0) const name = ref("why") // 1. watchEffect传入的函数默认会直接被执行 // 2. 在执行的过程中, 会自动的收集依赖(依赖哪些响应式的数据) watchEffect(() => { console.log("----", counter.value, name.value) }) return { counter, name } } } </script>
- 首先,
6. watchEffect 的停止侦听
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取
watchEffect
的返回值函数,调用该函数即可watch
方法也会返回一个停止侦听的函数比如在上面的案例中,我们
age
达到20
的时候就停止侦听:vue<template> <div> <h2>当前计数: {{ counter }}</h2> <button @click="counter++">+1</button> <button @click="name = 'kobe'">修改name</button> </div> </template> <script> import { watchEffect, watch, ref } from 'vue' export default { setup() { const counter = ref(0) const name = ref("why") // 1.watchEffect传入的函数默认会直接被执行 // 2.在执行的过程中, 会自动的收集依赖(依赖哪些响应式的数据) const stopWatch = watchEffect(() => { console.log("----", counter.value, name.value) if (counter.value >= 10) stopWatch() }) return { counter, name } } } </script>
五. 自定义 Hook 练习
1. useCounter

2. useTitle
编写一个修改 title
的 Hook
:

3. useScrollPosition
完成一个监听界面滚动位置的 Hook
:
import { ref } from 'vue'
export function useScrollPosition() {
const scrollX = ref(0)
const scrollY = ref(0)
document.addEventListener('scroll', () => {
scrollX.value = window.scrollX
scrollY.value = window.scrollY
})
return { scrollX, scrollY }
}
六. script setup 语法糖
1. script setup 语法
<script setup>
是在单文件组件SFC
中使用组合式API
的编译时语法糖,当同时使用SFC
与组合式API
时则推荐该语法- 更少的样板内容,更简洁的代码
- 能够使用纯
Typescript
声明prop
和抛出事件 - 更好的运行时性能
- 更好的
IDE
类型推断性能
使用这个语法,需要在
<script>
中添加setup
属性:vue<script setup> // ... </script>
里面的代码会被编译成组件
setup()
函数的内容:- 这意味着与普通的
<script>
只在组件被首次引入的时候执行一次不同 <script setup>
中的代码会在每次组件实例被创建的时候执行
- 这意味着与普通的
2. 顶层的绑定会被暴露给模板
当使用
<script setup>
的时候,任何在<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及import
引入的内容) 都能在模板中直接使用:vue<template> <div>{{ message }}</div> <button @click="changeMessage">修改message</button> </template> <script setup> // 1.所有编写在顶层中的代码, 都是默认暴露给template可以使用 import { ref, onMounted } from 'vue' // 2.定义响应式数据 const message = ref("Hello World") console.log(message.value) // 3.定义绑定的函数 function changeMessage() { message.value = "你好啊, 李银河!" } </script>
响应式数据需要通过
ref
、reactive
来创建
3. 导入的组件直接使用
<script setup>
范围里的值也能被直接作为自定义组件的标签名使用:vue<template> <show-info></show-info> </template> <script setup> // 所有编写在顶层中的代码, 都是默认暴露给 template 可以使用 import ShowInfo from './ShowInfo.vue' </script>
4. defineProps() 和 defineEmits()
- 为了在声明
props
和emits
选项时获得完整的类型推断支持 - 可以使用
defineProps
和defineEmits API
,它们将自动地在<script setup>
中可用: defineProps
方法返回props

注意:
传递给
defineProps
的泛型参数本身不能是一个导入的类型typescriptimport { Props } from './other-file' // 不支持! defineProps<Props>()
这是因为
Vue
组件是单独编译的,编译器目前不会抓取导入的文件以分析源类型。官方团队计划在未来的版本中解决这个限制
5. defineExpose()
使用
<script setup>
的组件是默认关闭的:- 通过模板
ref
或者$parent
链获取到的组件的公开实例,不会暴露任何在<script setup>
中声明的绑定
- 通过模板
可以通过
defineExpose
编译器宏来显式指定在<script setup>
组件中要暴露出去的property
:vue<script setup> import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b }) </script>
当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样
{ a: number, b: number }
(ref
会和在普通实例中一样被自动解包)