目录
前面我们已经学习了setup、reactive、ref、computed、watchEffect、watch、provide、inject
等等Composition API,那下面将通过一个Composition API的综合练习来巩固一下组合API的使用以及代码逻辑的封装(即Hook函数的封装)。其中该综合练习包含以下功能:
-
计数器案例的实现。
-
修改网页的标题。
-
完成一个监听界面滚动位置。
在使用Composition API之前,我们先看看用Options API是如何实现该功能。
代码如下所示:
<template>
<div>
<!--1.计数器案例 -->
<div>当前计数: {{counter}}</div>
<div>当前计数*2: {{doubleCounter}}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
export default {
data() {
return{
// 1.2计数器案例的逻辑代码
counter:100
}
},
computed: {
// 1.3计数器案例的逻辑代码
doubleCounter() {
return this.counter * 2
}
},
methods: {
// 1.4计数器案例的逻辑代码
increment() {
this.counter++;
},
decrement() {
this.counter--;
}
}
}
</script>
运行在浏览器的效果
下面我们再用Composition API来实现该功能。
代码如下所示
<template>
<div>
<!-- 1.计数器案例 -->
<div>当前计数: {{counter}}</div>
<div>当前计数*2: {{doubleCounter}}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
// 1.1计数器案例的逻辑代码
const counter = ref(100);
const doubleCounter = computed(() => counter.value * 2);
const increment = () => counter.value++;
const decrement = () => counter.value--;
return {
counter,
doubleCounter,
increment,
decrement
}
}
}
</script>
保存代码,运行在浏览器的效果和Options API实现的效果一模一样。通过这两个案例,可以发现:
-
Options API的特点就是在对应的属性中编写对应的功能模块
-
但Options API有一个很大的弊端是对应的代码逻辑被拆分到各个属性中,当组件变得更大、复杂时,同一个功能的逻辑会被拆分的很分散(如上面的计数器功能逻辑被拆分到各个选项中),不利于代码的阅读和理解。
-
Composition API的特点是能将同一个逻辑关注点相关的代码收集在一起,方便代码的封装和复用,也更利于代码的阅读和理解。
-
Composition API用了比较多的函数,用起来稍微比Options API复杂一点,但是函数式编程对TS支持更友好。
对比完Options API和Composition API编写计数器案例的优缺点之后,下面来看看如何对Composition API编写的代码逻辑进行封装和复用。在Options API编写方式中,我们已知道代码逻辑的封装和复用可以使用Mixin混入,那在Composition API中我们可以将关注点相关的代码逻辑封装到一个函数中,该函数我们一般会使用useXx
来命名,并且以useXx
开头的函数我们称之为自定义 Hook函数
useCounter
认识Hook函数之后,下面我们来把上面计数器案例的代码逻辑封装到一个useCounter
的Hook函数中。
封装useCounter Hook函数,代码如下所示:
import { ref, computed } from 'vue';
export default function useCounter() {
// 1.1计数器案例的逻辑代码
const counter = ref(100);
const doubleCounter = computed(() => counter.value * 2);
const increment = () => counter.value++;
const decrement = () => counter.value--;
return {
counter,
doubleCounter,
increment,
decrement
}
}
可以看到,我们在该文件中默认导出一个函数(也支持匿名函数),在该函数中我们把CompositionAPIExample组件实现计数器案例的代码逻辑全部抽取过来了。
接着修改CompositionAPIExample组件,代码如下所示:
<script>
import useCounter from './hooks/useCounter'
export default {
setup() {
// 1.计数器案例的代码逻辑抽取到useCounter hook 中了
const {counter, doubleCounter, increment, decrement} = useCounter()
return {counter, doubleCounter, increment, decrement}
}
}
</script>
可以看到,该组件之前实现计数器案例的逻辑代码已经抽取到了useCounter函数中,这时我们只要导入useCounter函数,并在setup中调用该函数便可以拿到返回的响应式数据和事件函数,然后直接返回给模板使用。保存代码,运行在浏览器的效果和没抽取前一模一样。
useTitle
实现完计数器案例之后,在CompositionAPIExample组件中来实现修改网页标题的功能。修改CompositionAPIExample组件,代码如下所示:
<script>
export default {
setup() {
// 修改网页的标题案例
const titleRef = ref("coder");
document.title = titleRef.value// 更新网页标题
}
</script>
可以看到,只在CompositionAPIExample中的setup函数中添加两行代码即可以。保存代码,运行在浏览器的效果,已经将网页的标题修改为coder。
像这种修改网页标题的代码逻辑可能在其它组件中还会再次使用到,那么我们就可以将该功能封装到一个Hook函数中。
建立一个useTitle.js文件封装useTitle Hook函数,代码如下所示:
import { ref, watch } from 'vue';
// 使用匿名函数,并该函数需接收一个参数
export default function(title = "默认的title") {
const titleRef = ref(title);
// 侦听titleRef变化,一旦被修改就更新
watch(titleRef, (newValue) => {
document.title = newValue
}, {
immediate: true // 侦听的回调函数先执行一次
})
return titleRef
}
修改CompositionAPIExample组件,代码如下所示:
<script>
import useTitle from './hooks/useTitle'
export default {
setup() {
// 修改网页的标题案例
const titleRef = useTitle("coder");
setTimeout(() => {
//3秒后修改titleRef的值,useTitle函数的watch侦听到会修改标题
titleRef.value = "why"
}, 3000);
}
}
</script>
可以看到,我们先导入useTitle函数,接着在setup中调用useTitle函数初始化标题为coder,然后过了2秒之后将标题修改为why。保存代码,运行在浏览器后。网页的标题在3秒后有coder修改为why。
useScrollPosition
实现完修改网页的标题之后,我们接着继续再CompositionAPIExample组件中来实现监听页面滚动位置的功能。修改CompositionAPIExample组件,代码如下所示:
<template>
<div>
<!-- 显示页面滚动位置 -->
<p style="width: 3000px;height: 5000px;">
width:3000px height:5000px的,模拟页面滚动
</p>
<div style="position: fixed;top:20px;right:20px">
<div >scrollX: {{scrollX}}</div>
<div >scrollY: {{scrollY}}</div>
</div>
</div>
</template>
<script>
export default {
setup() {
// 监听页面滚动
const scrollX = ref(0);
const scrollY = ref(0);
document.addEventListener("scroll", () => {
scrollX.value = window.scrollX;
scrollY.value = window.scrollY;
});
return {scrollX, scrollY}
}
}
</script>
可以看到,我们先在template中编写宽和高超出屏幕大小的p元素(模拟页面可滚动),接着在setup函数监听了页面的滚动,并在该回调函数中给scrollX和scrollY变量赋当前滚动的值。最后在return函数中返回scrollX和scrollY变量给temlpate来显示当前滚动的位置。保存代码,运行在浏览器的效果,上下滚动页面的时候,页面的右上角上能显示当前滚动位置值。
那如果该功能也会被再次使用到,我们依然可以将该功能封装到一个Hook函数中。
建立一个useScrollPosition.js文件封装useScrollPosition Hook函数,代码如下所示:
import { ref } from 'vue';
// 自定义 useScrollPosition Hook函数
export default function useScrollPosition() {
const scrollX = ref(0);
const scrollY = ref(0);
document.addEventListener("scroll", () => {
scrollX.value = window.scrollX;
scrollY.value = window.scrollY;
});
return {scrollX, scrollY} // 返回ref响应式数据
}
修改CompositionAPIExample组件,代码如下所示:
<script>
import useCounter from './hooks/useCounter'
import useTitle from './hooks/useTitle'
import useScrollPosition from './hooks/useScrollPosition'
export default {
setup() {
// 1.计数器案例(可直接解构,如果返回的是reactive对象则不能直接解构使用)
const {counter, doubleCounter, increment, decrement} = useCounter()
// 2.修改网页标题案例
const titleRef = useTitle("coder");
setTimeout(() => {
titleRef.value = "why"
}, 3000);
// 3.监听页面滚动位置案例 (可直接解构,因为Hook函数返回对象属性是ref对象)
const { scrollX, scrollY } = useScrollPosition();
return {counter, doubleCounter, increment, decrement, scrollX, scrollY}
}
}
</script>
可以看到,我们先导入useScrollPosition函数,接着在setup中调用useScrollPosition函数来获取到当前滚动的值。如果滚动页面了,useScrollPosition函数里会监听到并修改scrollX和scrollY响应式变量的值,同时更新页面。保存代码,运行在浏览器后。滚动网页时可以发现页面上右上角的scrollX和scrollY能显示当前滚动的位置。