如何在TypeScript中过滤API返回的JSON数组数据?
解决方案
首先,你的API返回数据是嵌套结构(数组包裹分类对象,分类对象的键为分类名,值为对应页面数组),这种结构不利于直接过滤。我们先将数据扁平化,再实现支持多条件的过滤逻辑,同时结合TypeScript保证类型安全。
1. 定义TypeScript类型
先明确数据结构的类型,避免类型错误:
// 单个页面的基础类型 interface Page { title: string; showTitle: string; description: string; page: string; tags: string[]; } // 扁平化后的条目,新增分类信息字段 interface PageWithCategory extends Page { category: string; }
2. 扁平化原始数据
将嵌套的分类-页面结构转换成带分类信息的平级数组,简化后续过滤逻辑:
const flattenPages = (rawData: Array<Record<string, Page[]>>): PageWithCategory[] => { return rawData.flatMap(categoryGroup => Object.entries(categoryGroup).flatMap(([category, pages]) => pages.map(page => ({ ...page, category })) ) ); };
3. 实现多条件过滤函数
编写支持按标题、描述关键词、标签、分类过滤的函数,可组合多个条件:
const filterPages = ( pages: PageWithCategory[], filters: { searchQuery?: string; selectedTags?: string[]; selectedCategory?: string; } ): PageWithCategory[] => { return pages.filter(page => { // 匹配标题/描述的关键词(不区分大小写) const matchesSearch = !filters.searchQuery || page.title.toLowerCase().includes(filters.searchQuery.toLowerCase()) || page.description.toLowerCase().includes(filters.searchQuery.toLowerCase()); // 匹配标签:支持任意标签匹配(若需全匹配可改成every) const matchesTags = !filters.selectedTags || page.tags.some(tag => filters.selectedTags!.includes(tag)); // 匹配分类 const matchesCategory = !filters.selectedCategory || page.category === filters.selectedCategory; // 所有条件满足才保留条目 return matchesSearch && matchesTags && matchesCategory; }); };
4. 在React组件中结合状态使用
结合React状态管理搜索关键词、选中的标签/分类,实时过滤数据:
import { useState, useEffect, useMemo } from 'react'; // 复用前面的类型定义 interface Page { /* ... */ } interface PageWithCategory extends Page { /* ... */ } const DocsFilter = () => { const [jsonData, setJsonData] = useState<Array<Record<string, Page[]>>>([]); const [searchQuery, setSearchQuery] = useState<string>(""); const [selectedTags, setSelectedTags] = useState<string[]>([]); const [selectedCategory, setSelectedCategory] = useState<string>(""); // 从API获取数据(复用你的原代码) useEffect(() => { fetch(`http://127.0.0.1:8000/api/docs`, { method: "GET", headers: { 'Accept': 'application/json' } }) .then(response => { if (response.status === 200) { return response.json(); } throw new Error("Documentation data cannot be located.") }) .then(data => setJsonData(data)) .catch(err => console.error(err)); }, []); // 缓存扁平化后的数据,避免重复计算 const flattenedPages = useMemo(() => flattenPages(jsonData), [jsonData]); // 缓存过滤后的结果,提升性能 const filteredPages = useMemo(() => filterPages(flattenedPages, { searchQuery, selectedTags, selectedCategory }), [flattenedPages, searchQuery, selectedTags, selectedCategory] ); return ( <div> {/* 搜索输入框 */} <input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="搜索标题/描述关键词..." /> {/* 分类选择器 */} <select value={selectedCategory} onChange={(e) => setSelectedCategory(e.target.value)} > <option value="">所有分类</option> {Array.from(new Set(flattenedPages.map(p => p.category))).map(category => ( <option key={category} value={category}>{category}</option> ))} </select> {/* 标签复选框组 */} <div> {Array.from(new Set(flattenedPages.flatMap(p => p.tags))).map(tag => ( <label key={tag}> <input type="checkbox" checked={selectedTags.includes(tag)} onChange={(e) => { setSelectedTags(prev => e.target.checked ? [...prev, tag] : prev.filter(t => t !== tag) ); }} /> {tag} </label> ))} </div> {/* 渲染过滤结果 */} <div> {filteredPages.length === 0 ? ( <p>没有找到匹配的内容</p> ) : ( <ul> {filteredPages.map((page, idx) => ( <li key={idx}> <h3>{page.showTitle} <small>({page.category})</small></h3> <p>{page.description}</p> <div>标签:{page.tags.join(", ")}</div> <a href={`/${page.page}`} target="_blank" rel="noopener noreferrer">查看文档</a> </li> ))} </ul> )} </div> </div> ); }; export default DocsFilter;
关键说明
- 扁平化数据:将原嵌套结构转换为平级数组是简化过滤逻辑的核心,让每个页面条目都携带分类信息,无需再处理嵌套遍历。
- 类型安全:TypeScript类型定义能提前发现数据结构相关错误,避免运行时问题。
- 性能优化:使用
useMemo缓存扁平化和过滤结果,防止每次渲染重复执行计算密集的操作。 - 规则灵活调整:过滤函数中的条件可按需修改,比如标签改成全匹配、搜索区分大小写等。
内容的提问来源于stack exchange,提问作者Owen Boreham
相关产品推荐
相关产品推荐

