You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3组合式API中如何仅用一个数组实现搜索与筛选功能

问题

我是Vue3新手,开发了一个调用NASA REST API获取小行星数据的应用,已实现按名称搜索、按近距离接近日期排序的功能,但当前代码维护了data、filteredData、filterByDate三个数组,想优化成仅操作一个原始数组,同时保留所有功能。以下是我的组件代码:

<template>
  <Loading v-if="isLoading" />
  <AsteroidLayout
    v-else
    :data="data"
    :fetchData="fetchData"
    :plusDay="plusDay"
    :changeDay="changeDay"
  >
    <div class="inputWrapper">
      <input
        type="text"
        class="search"
        placeholder="Search an asteroid by name"
        v-model="searchQuery"
      />
    </div>
    <button @click="sorted = !sorted">Filter by close approach date</button>
    <div v-if="sorted" v-for="asteroid in filterByDate" :key="asteroid.name">
      <Asteroids :asteroid="asteroid" />
    </div>
    <div v-for="asteroid in filteredData" :key="asteroid.id">
      <Asteroids :asteroid="asteroid" />
    </div>
  </AsteroidLayout>
</template>

<script>
import { onMounted, ref } from "vue";
import { addDays } from "date-fns";
import { computed } from "vue";
import Loading from "../components/Loading.vue";
import AsteroidLayout from "../components/AsteroidLayout.vue";
import Asteroids from "../components/Asteroids.vue";
export default {
  components: {
    Loading,
    AsteroidLayout,
    Asteroids,
  },
  setup() {
    const API_KEY = "Kzb0E64htPxZGEM33UC62hrug7mfHAzEzIH8Qyu1";
    const data = ref([]);
    const isLoading = ref(false);
    const sorted = ref(false);
    const plusDay = ref(0);
    const searchQuery = ref("");
    onMounted(() => {
      fetchData(data.value);
    });
    const fetchData = (type) => {
      isLoading.value = true;
      data.value = type;
      fetch(
        `https://api.nasa.gov/neo/rest/v1/feed?end_date=${getDate()}&&api_key=${API_KEY}`
      )
        .then((res) => res.json())
        .then((res) => {
          let fetchedData = res.near_earth_objects[getDate()];
          data.value = fetchedData;
          isLoading.value = false;
        })
        .catch(() =>
          alert(
            "Sorry, we can't load the asteroids data. Please try again later or check your internet connection"
          )
        );
    };

    const getDate = () => {
      return `${addDays(new Date(), plusDay.value).toISOString().substring(0, 10)}`;
    };

    const changeDay = (day) => {
      plusDay.value += day;
      fetchData(data.value);
    };
    const filteredData = computed(() => {
      return data.value.filter((asteroid) => {
        return asteroid.name.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1;
      });
    });
    const filterByDate = computed(() => {
      return data.value.sort((a, b) => {
        return (
          Date.parse(new Date(a.close_approach_data[0].close_approach_date_full)) -
          Date.parse(new Date(b.close_approach_data[0].close_approach_date_full))
        );
      });
    });
    return {
      data,
      isLoading,
      fetchData,
      plusDay,
      changeDay,
      filteredData,
      searchQuery,
      filterByDate,
      sorted,
    };
  },
};
</script>

<style scoped>
.inputWrapper {
  text-align: right;
}

.search {
  border: none;
  height: 30px;
  width: 200px;
  outline: none;
  border: 3px solid var(--secondary);
  border-radius: 35px;
  color: black;
  font-size: 14px;
  font-weight: 200;
  text-align: center;
}
</style>
解决方案

核心优化点

  1. 合并搜索与排序逻辑为单个计算属性,仅保留原始data数组作为唯一数据源
  2. 修复sort方法原地修改原始数组的问题(通过数组拷贝避免)
  3. 简化模板分支,统一渲染逻辑

修改后的完整代码

<template>
  <Loading v-if="isLoading" />
  <AsteroidLayout
    v-else
    :data="data"
    :fetchData="fetchData"
    :plusDay="plusDay"
    :changeDay="changeDay"
  >
    <div class="inputWrapper">
      <input
        type="text"
        class="search"
        placeholder="Search an asteroid by name"
        v-model="searchQuery"
      />
    </div>
    <button @click="sorted = !sorted">
      {{ sorted ? '取消日期排序' : '按近距离接近日期排序' }}
    </button>
    <!-- 统一使用合并后的计算属性渲染 -->
    <div v-for="asteroid in displayData" :key="asteroid.id">
      <Asteroids :asteroid="asteroid" />
    </div>
  </AsteroidLayout>
</template>

<script>
import { onMounted, ref, computed } from "vue";
import { addDays } from "date-fns";
import Loading from "../components/Loading.vue";
import AsteroidLayout from "../components/AsteroidLayout.vue";
import Asteroids from "../components/Asteroids.vue";
export default {
  components: {
    Loading,
    AsteroidLayout,
    Asteroids,
  },
  setup() {
    const API_KEY = "Kzb0E64htPxZGEM33UC62hrug7mfHAzEzIH8Qyu1";
    const data = ref([]);
    const isLoading = ref(false);
    const sorted = ref(false);
    const plusDay = ref(0);
    const searchQuery = ref("");
    
    onMounted(() => {
      fetchData();
    });

    const fetchData = () => {
      isLoading.value = true;
      fetch(
        `https://api.nasa.gov/neo/rest/v1/feed?end_date=${getDate()}&api_key=${API_KEY}`
      )
        .then((res) => res.json())
        .then((res) => {
          data.value = res.near_earth_objects[getDate()] || [];
          isLoading.value = false;
        })
        .catch(() =>
          alert(
            "抱歉,无法加载小行星数据,请稍后重试或检查网络连接"
          )
        );
    };

    const getDate = () => {
      return addDays(new Date(), plusDay.value).toISOString().slice(0, 10);
    };

    const changeDay = (day) => {
      plusDay.value += day;
      fetchData();
    };

    // 合并搜索过滤与日期排序的计算属性
    const displayData = computed(() => {
      // 第一步:按名称搜索过滤
      let result = data.value.filter(asteroid => 
        asteroid.name.toLowerCase().includes(searchQuery.value.toLowerCase())
      );
      
      // 第二步:如果开启排序,则按近距离日期排序(先拷贝数组避免修改原数据)
      if (sorted.value) {
        result = [...result].sort((a, b) => {
          const dateA = new Date(a.close_approach_data[0].close_approach_date_full);
          const dateB = new Date(b.close_approach_data[0].close_approach_date_full);
          return dateA - dateB;
        });
      }
      
      return result;
    });

    return {
      data,
      isLoading,
      fetchData,
      plusDay,
      changeDay,
      searchQuery,
      displayData, // 仅暴露合并后的计算属性
      sorted,
    };
  },
};
</script>

<style scoped>
.inputWrapper {
  text-align: right;
}

.search {
  border: none;
  height: 30px;
  width: 200px;
  outline: none;
  border: 3px solid var(--secondary);
  border-radius: 35px;
  color: black;
  font-size: 14px;
  font-weight: 200;
  text-align: center;
}
</style>

额外说明

  • 原fetchData函数中多余的type参数已移除,避免混淆
  • 排序逻辑中使用[...result]拷贝数组,防止sort方法原地修改原始数据
  • 按钮文本根据排序状态动态切换,提升用户体验
  • 使用includes替代indexOf,代码更简洁直观

内容的提问来源于stack exchange,提问作者Rahym Charyyev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:10:24