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>
解决方案
核心优化点
- 合并搜索与排序逻辑为单个计算属性,仅保留原始
data数组作为唯一数据源 - 修复
sort方法原地修改原始数组的问题(通过数组拷贝避免) - 简化模板分支,统一渲染逻辑
修改后的完整代码
<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
相关产品推荐
相关产品推荐

