没想到 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中watch的用法
vue中watch的用法
|
2天前
|
JavaScript 前端开发
vue动态添加style样式
vue动态添加style样式
|
1天前
|
JavaScript API
vue学习(13)监视属性
vue学习(13)监视属性
10 2
|
1天前
|
JavaScript
vue 函数化组件
vue 函数化组件
|
1天前
|
JavaScript 前端开发
vue学习(15)watch和computed
vue学习(15)watch和computed
9 1
|
1天前
|
JavaScript
vue学习(14)深度监视
vue学习(14)深度监视
10 0
|
JavaScript 测试技术 容器
Vue2+VueRouter2+webpack 构建项目
1). 安装Node环境和npm包管理工具 检测版本 node -v npm -v 图1.png 2). 安装vue-cli(vue脚手架) npm install -g vue-cli --registry=https://registry.
1037 0
|
2天前
|
JavaScript 前端开发
Vue项目使用px2rem
Vue项目使用px2rem
|
9天前
|
JavaScript 前端开发
vue学习(6)
vue学习(6)
30 9