没想到 Vue 中滥用 watch 危害这么大!吓得我连夜修改代码

简介: 没想到 Vue 中滥用 watch 危害这么大!吓得我连夜修改代码

引言



前端开发中,想必不少开发者都在 Vue 中使用过 watch 函数。但是你知道如果滥用 watch,会带来怎样的后果吗?今天我们一起聊聊这个问题,并去解决它!


使用watch的错误案例

简单例子

我们先来看一个简单的例子,这里我们有一个组件,需要在 props 变化时执行一些操作:

<template>
  <div>{{ dataList }}</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps(['value']);
const dataList = ref([]);
watch(
  () => props.value,
  (newValue, oldValue) => {
    // 根据新的 props.value 更新 dataList
    dataList.value = processValue(newValue);
  }
);
function processValue(value) {
  // 处理值的逻辑
  return value.split(',');
}
</script>

乍一看,这段代码似乎没什么问题,但随着业务逻辑的复杂化,watch 的数量和复杂度会迅速增加。

复杂业务逻辑

假设现在我们需要根据多个 props 的变化更新 dataList

<template>
  <div>{{ dataList }}</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps(['disableList', 'type', 'id']);
const dataList = ref([]);
watch(
  () => props.disableList,
  (newDisableList) => {
    dataList.value = processDisableList(newDisableList);
  },
  { deep: true }
);
watch(
  () => props.type,
  (newType) => {
    dataList.value = processType(newType);
  }
);
watch(
  () => props.id,
  (newId) => {
    fetchDataList(newId).then(newDataList => {
      dataList.value = newDataList;
    });
  },
  { immediate: true }
);
function processDisableList(disableList) {
  // 处理 disableList 的逻辑
  return disableList.map(item => item.id);
}
function processType(type) {
  // 处理 type 的逻辑
  return type.split(',');
}
function fetchDataList(id) {
  // 从服务器获取 dataList
  return fetch(`/api/dataList/${id}`).then(response => response.json());
}
</script>

在这个例子中,dataList 由多个 watch 控制,这些 watch 可能会相互影响,导致数据更新逻辑混乱。

 

使用watch带来的问题

逻辑分散

使用多个 watch 导致业务逻辑分散在多个函数中,难以维护。新同事接手时,往往需要花费大量时间理清各个 watch 之间的关系。

性能问题

每次 props 变化都会触发 watch,可能会带来性能问题,特别是在复杂的计算逻辑和频繁的数据更新中。

难以调试

watch 的回调函数可能会在意想不到的时机触发,导致调试变得困难,特别是在处理异步操作时。


解决滥用watch的方案

仅在必要时使用

watch 最主要的用途是监听特定数据变化并执行副作用操作。应尽量避免在 watch 中执行复杂的计算和业务逻辑。

使用 computed 替代

对于纯粹的计算逻辑,应使用 computed 属性,而非 watchcomputed 属性在依赖数据变化时会自动更新,并且是缓存的,只在依赖变化时重新计算。

<template>
  <div>{{ processedData }}</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps(['value']);
const data = ref(props.value);
const processedData = computed(() => {
  return data.value.split(',');
});
</script>

使用组合函数(Composition Functions)

在复杂的业务逻辑中,使用组合函数可以将逻辑模块化,使代码更易于维护和重用。

<template>
  <div>{{ processedData }}</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
const props = defineProps(['disableList', 'type', 'id']);
const dataList = ref([]);
const disableList = ref(props.disableList);
const type = ref(props.type);
const id = ref(props.id);
const processedData = useDataProcessing(disableList, type, id);
function useDataProcessing(disableList, type, id) {
  const data = ref([]);
  const processedDisableList = computed(() => {
    return disableList.value.map(item => item.id);
  });
  const processedType = computed(() => {
    return type.value.split(',');
  });
  watch(
    id,
    (newId) => {
      fetchDataList(newId).then(newDataList => {
        data.value = newDataList;
      });
    },
    { immediate: true }
  );
  return computed(() => {
    return processFinalData(processedDisableList.value, processedType.value, data.value);
  });
}
function fetchDataList(id) {
  return fetch(`/api/dataList/${id}`).then(response => response.json());
}
function processFinalData(disableList, type, dataList) {
  // 处理最终数据的逻辑
  return dataList.filter(item => !disableList.includes(item.id) && type.includes(item.type));
}
</script>

案例实践

案例背景

假设我们有一个电商平台,需要根据用户选择的过滤条件动态更新商品列表。用户可以选择不同的分类、价格区间以及排序方式。

错误的实现方式

下面是一个过度使用 watch 的实现方式:

<template>
  <div>
    <Filter @change="handleFilterChange" />
    <ProductList :products="filteredProducts" />
  </div>
</template>
<script setup>
import { ref, watch } from 'vue';
import Filter from './Filter.vue';
import ProductList from './ProductList.vue';
const filters = ref({
  category: '',
  priceRange: [0, 100],
  sortBy: 'price'
});
const products = ref([]);
const filteredProducts = ref([]);
watch(
  () => filters.value.category,
  (newCategory) => {
    filteredProducts.value = filterProducts(products.value, filters.value);
  }
);
watch(
  () => filters.value.priceRange,
  (newPriceRange) => {
    filteredProducts.value = filterProducts(products.value, filters.value);
  }
);
watch(
  () => filters.value.sortBy,
  (newSortBy) => {
    filteredProducts.value = sortProducts(filteredProducts.value, filters.value.sortBy);
  }
);
function filterProducts(products, filters) {
  // 根据 filters 过滤产品列表的逻辑
  return products.filter(product => {
    return product.category === filters.category && product.price >= filters.priceRange[0] && product.price <= filters.priceRange[1];
  });
}
function sortProducts(products, sortBy) {
  // 根据 sortBy 对产品列表排序的逻辑
  return products.sort((a, b) => {
    if (sortBy === 'price') {
      return a.price - b.price;
    } else if (sortBy === 'name') {
      return a.name.localeCompare(b.name);
    }
  });
}
</script>

优化的实现方式

下面是使用 computed 和组合函数的优化实现方式:

<template>
  <div>
    <Filter @change="handleFilterChange" />
    <ProductList :products="filteredProducts" />
  </div>
</template>
<script setup>
import { ref, computed } from 'vue';
import Filter from './Filter.vue';
import ProductList from './ProductList.vue';
const filters = ref({
  category: '',
  priceRange: [0, 100],
  sortBy: 'price'
});
const products = ref([]);
const filteredProducts = useFilteredProducts(products, filters);
function useFilteredProducts(products, filters) {
  const filtered = computed(() => {
    return filterProducts(products.value, filters.value);
  });
  const sorted = computed(() => {
    return sortProducts(filtered.value, filters.value.sortBy);
  });
  return sorted;
}
function filterProducts(products, filters) {
  // 根据 filters 过滤产品列表的逻辑
  return products.filter(product => {
    return product.category === filters.category && product.price >= filters.priceRange[0] && product.price <= filters.priceRange[1];
  });
}
function sortProducts(products, sortBy) {
  // 根据 sortBy 对产品列表排序的逻辑
  return products.sort((a, b) => {
    if (sortBy === 'price') {
      return a.price - b.price;
    } else if (sortBy === 'name') {
      return a.name.localeCompare(b.name);
    }
  });
}
</script>


总结

在实际开发中,我们应尽量减少对 watch 的依赖,更多地使用 computed 来处理数据的同步更新。如果确实需要监听复杂的业务逻辑变化,可以考虑将逻辑模块化,通过组合函数实现,这样不仅使代码更清晰,还能有效减少 bug 的产生。

相关文章
|
2月前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
147 1
|
6天前
|
移动开发 JavaScript API
Vue Router 核心原理
Vue Router 是 Vue.js 的官方路由管理器,用于实现单页面应用(SPA)的路由功能。其核心原理包括路由配置、监听浏览器事件和组件渲染等。通过定义路径与组件的映射关系,Vue Router 将用户访问的路径与对应的组件关联,支持哈希和历史模式监听 URL 变化,确保页面导航时正确渲染组件。
|
10天前
|
监控 JavaScript 前端开发
ry-vue-flowable-xg:震撼来袭!这款基于 Vue 和 Flowable 的企业级工程项目管理项目,你绝不能错过
基于 Vue 和 Flowable 的企业级工程项目管理平台,免费开源且高度定制化。它覆盖投标管理、进度控制、财务核算等全流程需求,提供流程设计、部署、监控和任务管理等功能,适用于企业办公、生产制造、金融服务等多个场景,助力企业提升效率与竞争力。
61 12
|
6天前
|
JavaScript 前端开发 开发者
Vue中的class和style绑定
在 Vue 中,class 和 style 绑定是基于数据驱动视图的强大功能。通过 class 绑定,可以动态更新元素的 class 属性,支持对象和数组语法,适用于普通元素和组件。style 绑定则允许以对象或数组形式动态设置内联样式,Vue 会根据数据变化自动更新 DOM。
|
6天前
|
JavaScript 前端开发 数据安全/隐私保护
Vue Router 简介
Vue Router 是 Vue.js 官方的路由管理库,用于构建单页面应用(SPA)。它将不同页面映射到对应组件,支持嵌套路由、路由参数和导航守卫等功能,简化复杂前端应用的开发。主要特性包括路由映射、嵌套路由、路由参数、导航守卫和路由懒加载,提升性能和开发效率。安装命令:`npm install vue-router`。
|
28天前
|
JavaScript 安全 API
iframe嵌入页面实现免登录思路(以vue为例)
通过上述步骤,可以在Vue.js项目中通过 `iframe`实现不同应用间的免登录功能。利用Token传递和消息传递机制,可以确保安全、高效地在主应用和子应用间共享登录状态。这种方法在实际项目中具有广泛的应用前景,能够显著提升用户体验。
54 8
|
28天前
|
存储 设计模式 JavaScript
Vue 组件化开发:构建高质量应用的核心
本文深入探讨了 Vue.js 组件化开发的核心概念与最佳实践。
74 1
|
3月前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
111 58
|
2月前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
3月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
67 1
vue学习第一章

热门文章

最新文章