首页 > 其他分享 >答应我,在vue中不要滥用watch好吗?

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

时间:2024-02-29 20:01:53浏览次数:21  
标签:逻辑 vue const 滥用 dataList 代码 watch props

前言

上周五晚上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中。

如果我的文章对你有点帮助,欢迎关注公众号:【前端欧阳】,文章在公众号首发。你的支持就是我创作的最大动力,感谢感谢!

标签:逻辑,vue,const,滥用,dataList,代码,watch,props
From: https://www.cnblogs.com/heavenYJJ/p/18045325

相关文章

  • Vue Router系列之(九)编程式路由导航
    编程式路由导航作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活。为什么需要除了<router-link>标签之外实现路由跳转的方式?<router-link>标签最后变为<a>标签,无法实现button按钮的跳转触发路由跳转可能需要异步触发,<router-link>标签也无法实现具体编码://$ro......
  • Vue Router系列之(十二)路由守卫
    路由守卫作用:对路由进行权限控制,满足一定条件才能看到跳转到的路由组件分类:全局守卫、独享守卫、组件内守卫全局守卫://src/router/index.js//router:我们创建的路由器实例//router.beforeEach(function):每一次进行路由跳转之前都会帮我们调用我们传入的函数//全......
  • Vue CLI 系列之(十)webStorage
    webStorage【浏览器本地存储】localStorage和sessionStorage统称为webStorage1.localStorage未登录账号的情况下进行了商品搜索,搜索历史中保存了之前的搜索记录借助浏览器的本地存储可以将数据存到硬盘上,用于缓存数据通过浏览器如何查看浏览器本地存储​ 每个网站都有自己......
  • Vue CLI 系列之(十四)$nextTick
    $nextTick$nextTick这也是一个生命周期​ 如果要实现一个input框点击后进行一些数据修改的操作【这里是指能够引起Vue重新解析模板的数据修改操作,比如修改data中的数据】,然后让该input框获取焦点,该怎么做?//Item组件中有一个input输入框和一个编辑按钮,实现点击按钮后input框显......
  • Vue CLI 系列之(十三)消息订阅与发布
    消息订阅与发布【pubsub】1.理解消息订阅与发布2.原理图​ 通过报纸的订阅与发布来理解就是:A去C那订阅了报纸demo,并说明了自己的住址test,由于test是定义在A中的,而A又把test的引用提供出来了,这样C只要一调用test,自动就来到了A这,也就找到了A住的地儿,C发布test报纸时携带的内......
  • Vue CLI 系列之(十二)全局事件总线
    全局事件总线【GlobalEventBus】......
  • Vue CLI 系列之(十一)组件自定义事件
    组件自定义事件区别于js内置事件,内置事件是给html元素用的,而自定义事件是给组件用的1.给组件实例对象绑定自定义事件给组件绑定自定义事件v-on:自定义事件名="回调函数"比如:<Studentv-on:atguigu="demo"/>上面代码的含义为:由于v-on是在Student这个组件标签上,所以是给stu......
  • Vue CLI 系列之(十五)过渡与动画
    过渡与动画Vue封装的过渡与动画1.前置知识CSS3动画【2D转换、3D转换、过渡、动画】参考网站:https://www.runoob.com/css3/css3-animations.html<h1v-show="isShow"id="title">显示了</h1><style> h1{ /*通过animation把"donghua"动画捆绑到h1元素,时长:1......
  • Vue 2x 系列之(十五)过滤器
    过滤器BootCDN:包含了一些免费、优秀的第三方类库官网:https://www.bootcdn.cn/moment.js:js的日期处理类库dayjs:moment.js的轻量化解决方案,API同moment.js完全一致引入dayjs,全局就多了一个dayjs()函数,dayjs()函数默认解析当前时间的时间戳,也可传入时间戳进行解析注:过滤器相对......
  • Vue 2x 系列之(十七)自定义指令
    自定义指令从某种程度上来说,Vue中的自定义指令就是把原生DOM操作进行了一次封装指令是不能脱离元素【标签】存在的定义指令:big使用指令:v-big值的写法:对象【可以处理一些细节上的问题】和函数定义指令可以通过两种方式,函数式和对象式1.函数式指令名(真实DOM元素[element],......