当前位置:首页 > 技术文章 > 正文内容

Vue3 超进化:两个改变编码习惯的高级技巧

有这两个能够帮我们“打通任督二脉”的Vue3高级技巧,掌握它们,将提高我们对底层原理的理解以及解决复杂问题的能力

封装的艺术 —— 随心所欲地创建 Composable函数 (useXXX)

Composable是一个利用Vue组合式API来封装和复用有状态逻辑的函数。按照惯例,我们通常以use开头命名,例如useMousePositionuseFetch。它不是一个特定的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

在熟练掌握 refreactive 之后,随着复杂功能或优化性能的出现,需要在必要时“绕过”或“定制”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>

将这两个技巧融入我们的日常开发中,刻意练习,我们的代码质量和个人技术深度,必将迈上一个新的台阶。

相关文章

财务主管花了一周时间自制费用报销管理系统,是我见过最好用的

公司的费用报销又多又乱,一不小心就出错!头疼,财务主管花了一周时间自制费用报销管理台账,分类统计,重复报销还能自动提醒,真的少了很多麻烦!费用报销是财务日常工作必会面对的,各种票据太多太乱,搞的很烦,...

八款值得尝试的精美的Linux发行版,你用过哪几款?

Linux发行版各式各样,每个发行版都有自己的特点,在这篇文章中,将会列出让一些另 Linux 用户印象最深刻且精美的 Linux 发行版,包括对初学者友好和流行的发行版。elementary OSe...

细数5款国外热门Linux发行版(linux发行版排名网站)

Linux系统已经与我们的生活息息相关,当你用Android手机浏览这篇文章时,你就已经在使用Linux系统。当然作为编程开发最热门的系统,他还有很多专注于开发使用的版本。Fedora热门入门推荐,一...

Vue3 中,父子组件如何传递参数?(vue父子组件传递数据方法)

在 Vue3 中,组件化开发是非常重要的特征,那么组件之间传值就是开发中常见的需求了。组件之间的传值三种方式:父传子、子传父、非父子组件传值。一、父传子( defineProps )父组件主要通过使用...

2024前端面试真题之—VUE篇(前端面试题vuex)

添加图片注释,不超过 140 字(可选)1.vue的生命周期有哪些及每个生命周期做了什么? beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、com...

vue3-内置组件-Teleport(vue内置指令有哪些)

Teleport<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。基本用法有时我们可能会遇到这样的场景:一个组件模板的一部...