Vue3 超进化:两个改变编码习惯的高级技巧
有这两个能够帮我们“打通任督二脉”的Vue3高级技巧,掌握它们,将提高我们对底层原理的理解以及解决复杂问题的能力。
封装的艺术 —— 随心所欲地创建 Composable函数 (useXXX)
Composable是一个利用Vue组合式API来封装和复用有状态逻辑的函数。按照惯例,我们通常以use开头命名,例如useMousePosition、useFetch。它不是一个特定的API,而是一种代码组织模式。
- 告别Mixin的混乱:相比Vue2的Mixin,Composable的来源清晰,不存在命名冲突问题,类型推导也更友好。我们知道每一个响应式状态和方法都来自于哪个use函数。
- 逻辑内聚,关注点分离:我们可以将一个复杂组件中的不同逻辑(如数据请求、事件监听、动画控制)拆分到不同的Composable中,让组件的<script setup>部分变得异常清爽,只负责“组织”逻辑,而非“实现”逻辑。
创建一个useDebouncedRef
假设我们有一个搜索框,希望在用户停止输入500毫秒后再去请求API,以避免频繁的请求。这个“防抖”逻辑在很多地方都会用到。
1. 创建 useDebouncedRef.ts
// composables/useDebouncedRef.ts
import { customRef } from 'vue';
/**
* 创建一个防抖的 ref
* @param value 初始值
* @param delay 延迟毫秒数, 默认 500ms
*/
export function useDebouncedRef<T>(value: T, delay = 500) {
let timeout: number;
// customRef 是一个强大的API, 允许我们完全控制 ref 的依赖跟踪和更新触发
return customRef((track, trigger) => {
return {
get() {
track(); // 告诉Vue, 这个值被读取了, 需要追踪依赖
return value;
},
set(newValue: T) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger(); // 告诉Vue, 值已经改变, 请更新DOM
}, delay);
},
};
});
}
注意: 这里我们顺便用到了下一个技巧的核心API customRef,稍后会详细讲解。
2. 在组件中使用
<template>
<input v-model="searchText" placeholder="输入后500ms触发更新..." />
<p>Debounced Value: {{ searchText }}</p>
</template>
<script setup lang="ts">
import { useDebouncedRef } from './composables/useDebouncedRef';
// 使用起来就像一个普通的 ref,但它自带防抖功能!
const searchText = useDebouncedRef('', 500);
// 我们可以像往常一样 watch 它,回调只会在防抖后执行
// watch(searchText, (newValue) => {
// console.log('向服务器发起请求: ', newValue);
// });
</script>
掌控响应式 —— 玩转customRef与shallowRef
在熟练掌握 ref和reactive 之后,随着复杂功能或优化性能的出现,需要在必要时“绕过”或“定制”Vue的默认深度响应式行为。
- customRef: 创建一个自定义的ref,并对其依赖跟踪和更新触发进行显式控制。我们上面的useDebouncedRef就是最佳范例。
- shallowRef: 创建一个只对.value的赋值操作是响应式的ref。其内部的值不会被深层地转换为响应式对象。
- customRef体现了我们对响应式原理的理解:我们知道响应式系统工作的两个核心是track()(依赖收集)和trigger()(更新触发)。能够使用customRef意味着我们已经从API使用者变成了API“创造者”。
- shallowRef是性能优化的利器:当我们有大型的、不可变的数据结构时(例如一个巨大的JSON对象、一个第三方库的实例),使用ref会对其进行深度递归代理,造成不必要的性能开销。此时shallowRef就是最佳选择。
使用shallowRef优化大型数据
假设我们需要引入一个庞大的图表库实例,这个实例本身有很多内部属性,但我们只关心实例本身是否被替换。
<template>
<div ref="chartContainer"></div>
</template>
<script setup lang="ts">
import { onMounted, shallowRef, ref, triggerRef } from 'vue';
import SomeHeavyChartLibrary from 'some-heavy-chart-library';
// 使用 shallowRef, Vue 不会尝试代理 aChartInstance 内部的所有属性
const chartInstance = shallowRef(null);
const chartContainer = ref(null);
onMounted(() => {
// aChartInstance 的赋值是响应式的
chartInstance.value = new SomeHeavyChartLibrary(chartContainer.value);
});
function updateChartWithOptions(newOptions) {
if (chartInstance.value) {
// chartInstance 内部属性的改变不会触发更新
chartInstance.value.setOptions(newOptions);
// 如果我们确实需要手动强制更新,可以使用 triggerRef
// triggerRef(chartInstance);
}
}
</script>
将这两个技巧融入我们的日常开发中,刻意练习,我们的代码质量和个人技术深度,必将迈上一个新的台阶。