答应我,在vue中不要滥用watch好吗?

前端Q我是winty,专注分享前端知识和各类前端资源,乐于分享各种有趣的事,关注我,一起做个有趣的人~公众号

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

前言

上周五晚上8点,开开心心的等着产品验收完毕后就可以顺利上线。结果产品突然找到我说要加需求,并且维护这一块业务的同事已经下班走了,所以只有我来做。虽然内心一万头草泥马在狂奔,但是嘴里还是一口答应没问题。由于这一块业务很复杂并且我也不熟悉,加上还饿着肚子,在梳理代码逻辑的时候我差点崩溃了。需要修改的那个vue文件有几千行代码,迭代业务对应的ref变量有10多个watch。我光是梳理这些watch的逻辑就搞了很久,然后小心翼翼的在原有代码上面加上新的业务逻辑,不敢去修改原有逻辑(担心搞出线上bug背锅)。

滥用watch带来的问题

首先我们来看一个例子:

<template>
  {{ dataList }}
</template>

<script setup lang="ts">
import { ref, watch } from "vue";

const dataList = ref([]);
const props = defineProps(["disableList""type""id"]);
watch(
  () => props.disableList,
  () => {
    // 根据disableList逻辑很复杂同步计算出新list
    const newList = getListFromDisabledList(dataList.value);
    dataList.value = newList;
  },
  { deep: true }
);
watch(
  () => props.type,
  () => {
    // 根据type逻辑很复杂同步计算出新list
    const newList = getListFromType(dataList.value);
    dataList.value = newList;
  }
);
watch(
  () => props.id,
  () => {
    // 从服务端获取dataList
    fetchDataList();
  },
  { immediate: true }
);
</script>

上面这个例子在template中渲染了dataList,当props.id更新时和初始化时从服务端异步获取dataList。当props.disableListprops.type更新时,同步的计算出新的dataList。

代码逻辑流程图是这样的:

乍一看上面的代码没什么问题,但是当一个不熟悉这一块业务的新同学接手这一块代码时问题就出来了。

我们平时接手一个不熟悉的业务首先要找一个切入点,对于前端业务,切入点肯定是浏览器渲染的页面。在 Vue 中,页面由模板渲染而来,找到模板中使用的响应式变量和他的来源,就能理解业务逻辑。以 dataList 变量为例,梳理dataList的来源基本就可以理清业务逻辑。

在我们上面的这个例子dataList的来源就是发散的,有很多个来源。首先是watchprops.id从服务端异步获取。然后是watchprops.disableListprops.type,同步更新了dataList。这个时候一个不熟悉业务的同学接到产品需求要更新dataList的取值逻辑,他需要先熟悉dataList多个来源的取值逻辑,熟悉完逻辑后再分析我到底应该是在哪个watch上面去修改业务逻辑完成产品需求。

但是实际上我们维护别人的代码时(特别是很复杂的代码)一般都不愿意去改代码,而是在原有代码的基础上再去加上我们的代码。因为去改别人的复杂代码很容易搞出线上bug,然后背锅。所以在这里我们的做法一般都是再加一个watch,然后在这个watch中去实现产品最新的dataList业务逻辑。

watch(
  () => props.xxx,
  () => {
    // 加上产品最新的业务逻辑
    const newList = getListFromXxx(dataList.value);
    dataList.value = newList;
  }
);

迭代几次业务后这个vue文件里面就变成了一堆watch,屎山代码就是这样形成的。当然不排除有的情况是故意这样写的,为的就是稳定自己在团队里面的地位,因为离开了你这坨代码没人敢动。

使用computed解决问题

我们看了上面的反例,那么一个易维护的代码是怎么样的呢?我认为应该是下面这样的:

dataListtemplate中渲染,然后同步更新dataList,最后异步从服务端异步获取dataList,整个过程能够被穿成一条线。此时新来一位同学要去迭代dataList相关的业务,那么他只需要搞清楚产品的最新需求是应该在同步阶段去修改代码还是异步阶段去修改代码,然后在对应的阶段去加上对应的最新代码即可。

我们来看看上面的例子应该怎么优化成易维护的代码,上面的代码中dataList来源主要分为同步来源和异步来源。异步来源这一块我们没法改,因为从业务上来看props.id更新后必须要从服务端获取最新的dataList。我们可以将同步来源的代码全部摞到computed中。优化后的代码如下:

<template>
  {{ renderDataList }}
</template>

<script setup lang="ts">
import { ref, computed, watch } from "vue";

const props = defineProps(["disableList""type""id"]);
const dataList = ref([]);

const renderDataList = computed(() => {
  // 根据disableList计算出list
  const newDataList = getListFromDisabledList(dataList.value);
  // 根据type计算出list
  return getListFromType(newDataList);
});

watch(
  () => props.id,
  () => {
    // 从服务端获取dataList
    fetchDataList();
  },
  {
    immediate: true,
  }
);
</script>

我们在template中渲染的不再是dataList变量,而是renderDataListrenderDataList是一个computed,在这个computed中包含了所有dataList同步相关的逻辑。代码逻辑流程图是这样的:

此时一位新同学接到产品需求要迭代dataList相关的业务,因为我们的整个业务逻辑已经变成了一条线,新同学就可以很快的梳理清楚业务逻辑。再根据产品的需求看到底应该是修改同步相关的逻辑还是异步相关的逻辑。下面这个是修改同步逻辑的demo:

const renderDataList = computed(() => {
  // 加上产品最新的业务逻辑
  const xxxList = getListFromXxx(dataList.value);
  // 根据disableList计算出list
  const newDataList = getListFromDisabledList(xxxList);
  // 根据type计算出list
  return getListFromType(newDataList);
});

总结

这篇文章介绍了watch主要分为两种使用场景,一种是当watch的值改变后需要同步更新渲染的dataList,另外一种是当watch的值改变后需要异步从服务端获取要渲染的dataList。如果不管同步还是异步都一股脑的将所有代码都写在watch中,那么后续接手的维护者要梳理dataList相关的逻辑就会非常痛苦。因为到处都是watch在更新dataList的值,完全不知道应该在哪个watch中去加上最新的业务逻辑,这种时候我们一般就会再新加一个watch然后在新的watch中去实现最新的业务逻辑,时间久了代码中就变成了一堆watch,维护性就变得越来越差。我们给出的优化方案是将那些同步更新dataListwatch代码全部摞到一个名为renderDataListcomputed,后续维护者只需要判断新的业务如果是同步更新dataList,那么就将新的业务逻辑写在computed中。如果是要异步更新dataList,那么就将新的业务逻辑写在watch中。

往期推荐


如何减少网页卡顿
突破项目瓶颈:2024 年 Monorepo 工具选择和实践
SessionStorage 90%的前端都未知的小秘密!

最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

前端Q本公众号主要分享一些技术圈(前端圈为主)相关的技术文章、工具资源、学习资料、招聘信息及其他有趣的东西...公众号

点个在看支持我吧

相关推荐

  • 目前工资最高的几家外包公司汇总!(2024最新版)
  • 畅销10W册的《码农翻身》出第二部啦
  • 万字:深度解析“外卡收单”体系
  • 腾讯文档收集表后台重构:改造一个巨石单体!
  • 告别 BeanUtil.copyProperties,这款IDEA插件才是最优的替代方案!
  • 真是太凡尔赛了!一位女硕士研究生被省烟草局提前录取了,但却高兴不起来,自己学的是计算机,华为也发了offer。。。
  • Java 22 正式发布,一文了解全部新特性
  • JDK22 正式发布了 !
  • 推荐一款超高颜值的开源服务器仪表盘,非常不错!
  • 大模型提示学习样本量有玄机,自适应调节方法好
  • 大模型增量预训练新技巧-解决灾难性遗忘
  • ChatGPT参数规模被扒:只有7B
  • Kaggle知识点:sktime时序建模库
  • [开源]前后端分离的企业级内容管理系统,站群管理、多平台静态化
  • 3D重建范式变革!最新模型MVDiffusion++:无需相机姿态,即可实现高清3D模型重建
  • Stable Video 3D震撼上线,视频扩散模型史诗级提升!4090可跑,权重已开放
  • 老黄再曝惊人语录:LLM幻觉有解,AGI五年内必来!
  • 微软一夜吞噬估值40亿美金AI初创!2位联创加盟重点打造Copilot,还带走多数员工
  • 英伟达引爆AI PC革命,RTX给1亿打工人送算力!40系卡秒生百张图,「黑神话:悟空」画质爆表玩家颤抖
  • 2024 年 3 月编程语言排行榜,Python 与其他语言之间的差距从未如此之大!