TypeScript实现嵌套事件数据的动态键(city/price)过滤
嵌套演唱会事件数据的动态键过滤功能完善
需求背景
需要实现一个TypeScript函数,根据动态键(city和price)对嵌套结构中的演唱会事件数据进行过滤,输入框值变化时触发过滤逻辑。
原始嵌套数据
{"id":3,"name":"Concerts","events":[],"children":[{"id":1207,"name":"Rock and Pop","events":[],"children":[{"id":429,"name":"Backstreet Boys","children":[],"events":[{"id":4422829,"name":"Backstreet Boys","venueName":"Freedom Mortgage Pavilion","city":"Camden","price":23,"distanceFromVenue":131162.390908814,"date":"Jul 16 2020"},{"id":4422826,"name":"Backstreet Boys","venueName":"Xfinity Theatre","city":"Hartford","price":20,"distanceFromVenue":160356.993027791,"date":"Jul 17 2022"},{"id":150029205,"name":"Backstreet Boys","date":"Jul 24 2022","venueName":"Bethel Woods Center for the Arts","city":"Bethel","price":69,"distanceFromVenue":122298.256317016}]},{"name":"Justin Bieber","id":450948,"children":[],"events":[{"id":8621837,"name":"Justin Bieber","date":"Jul 17 2022","venueName":"The OVO Hydro (formerly The SSE Hydro)","city":"Glasgow","price":97,"distanceFromVenue":5181466.9468135},{"id":8621843,"name":"Justin Bieber","date":"Jul 24 2022","venueName":"The O2","city":"London","price":138,"distanceFromVenue":5581760.89658226},{"id":8621842,"name":"Justin Bieber","date":"Jul 24 2022","venueName":"The O2","city":"London","price":110,"distanceFromVenue":5581760.89658226}]},{"name":"Elton John","id":43088,"children":[],"events":[{"id":6216095,"name":"Elton John","date":"Jul 30 2022","venueName":"Progressive Field","city":"Cleveland","price":133,"distanceFromVenue":643919.392660771},{"id":6222261,"name":"Elton John","dayOfWeek":"Fri","date":"Aug 05 2022","venueName":"Soldier Field","city":"Chicago","price":91,"distanceFromVenue":1138316.92169974},{"id":6217165,"name":"Elton John","date":"Sep 07 2022","venueName":"Rogers Centre","city":"Chicago","price":108,"distanceFromVenue":1138316.92169974}]}]},{"id":1026,"name":"Rap/Hip-Hop/Reggae","events":[],"children":[{"name":"Rage Against the Machine","id":100920451,"children":[],"events":[{"id":4423330,"name":"Rage Against the Machine","date":"Aug 09 2022","venueName":"Madison Square Garden","city":"New York","price":103,"distanceFromVenue":5571576.14652457},{"id":4430490,"name":"Rage Against The Machine with Run The Jewels","url":"/rage-against-the-machine-new-york-tickets-8-11-2022/event/104589398/","date":"Aug 11 2022","venueName":"Madison Square Garden","city":"New York","price":95,"distanceFromVenue":5571576.14652457},{"id":4922928,"name":"Rage Against The Machine & Run The Jewels","date":"Aug 12 2022","venueName":"Madison Square Garden","city":"New York","price":160,"distanceFromVenue":5571576.14652457}]},{"name":"Daddy Yankee","id":66913,"children":[],"events":[{"id":10278327,"name":"Daddy Yankee","date":"Aug 12 2022","venueName":"SAP Center","city":"San Jose","price":130,"distanceFromVenue":8640632.67448427},{"id":10275468,"name":"Daddy Yankee","date":"Aug 13 2022","venueName":"The Kia Forum","city":"Inglewood","price":37,"distanceFromVenue":8776920.13120377},{"id":10380132,"name":"Daddy Yankee","date":"Aug 15 2022","venueName":"The Kia Forum","city":"Inglewood","price":16,"distanceFromVenue":8776920.13120377}]}]},{"id":1027,"name":"R&B/Urban Soul","events":[],"children":[{"name":"Chris Brown","children":[],"id":106817,"events":[{"id":150100779,"name":"Chris Brown & Lil Baby","date":"Aug 12 2022","venueName":"iTHINK Financial Amphitheatre","city":"West Palm Beach","price":19,"distanceFromVenue":7057650.78884398},{"id":150096789,"name":"Chris Brown & Lil Baby","date":"Aug 13 2022","venueName":"MidFlorida Credit Union Amphitheatre at the Florida State Fairgrounds","city":"Tampa","price":28,"distanceFromVenue":7099615.41662152}]}]}]}
现有初始代码
const filterBy = ( data: EventRootObject | undefined, name: string, value: string ): EventRootObject | undefined => { if (data) { const filteredEvent = data.events.filter((event: EventType) => event[name].match(value) ); data.events = filteredEvent; const childs = data.children?.map((child: any) => { return filterBy(child, name, value); }); data.children = childs; return data; } }; const handleOnChange = (e: any) => { const result = filterBy(orignialEventsList, e.target.name, e.target.value); console.log('result ', result); setEventList(result); };
原始类型定义
export interface EventType { id: number; name: string; venueName: string; city: string; price: number; distanceFromVenue: number; date: string; dayOfWeek: string; url: string; } export interface EventRootObject { id: number; name: string; events: EventType[] | []; children: any[]; }
现有代码问题分析
- 污染原始数据:直接修改传入的
data对象,导致后续过滤无法基于初始数据重新计算。 - 类型不安全:
event[name]无类型约束,且price是数字类型,直接调用字符串方法match会报错。 - 过滤逻辑缺陷:空输入时会过滤掉所有事件,未区分字符串(
city)和数字(price)的匹配逻辑。 - 子节点处理粗糙:
map后可能返回undefined,未过滤无效子节点。 - 事件类型缺失:
handleOnChange的参数e用any,不符合TypeScript规范。
完善后的实现
1. 修正类型定义
优化类型准确性,避免any并标记可选字段:
export interface EventType { id: number; name: string; venueName?: string; city: string; price: number; distanceFromVenue: number; date: string; dayOfWeek?: string; url?: string; } export interface EventRootObject { id: number; name: string; events: EventType[]; children: EventRootObject[]; }
2. 重构filterBy纯函数
实现不修改原数据的纯函数,适配不同类型字段的过滤逻辑:
const filterBy = ( data: EventRootObject | undefined, key: keyof EventType, value: string ): EventRootObject | undefined => { if (!data) return undefined; const filterValue = value.trim(); let filteredEvents = [...data.events]; // 非空值时执行过滤 if (filterValue) { filteredEvents = data.events.filter(event => { const eventValue = event[key]; // 字符串类型(如city):大小写不敏感的模糊匹配 if (typeof eventValue === 'string') { return eventValue.toLowerCase().includes(filterValue.toLowerCase()); } // 数字类型(如price):转为字符串做模糊匹配,也可改为范围匹配(如<=输入值) if (typeof eventValue === 'number') { return eventValue.toString().includes(filterValue); } return true; }); } // 递归处理子节点,过滤掉无效的子节点 const filteredChildren = data.children .map(child => filterBy(child, key, filterValue)) .filter((child): child is EventRootObject => child !== undefined); // 返回新对象,不修改原始数据 return { ...data, events: filteredEvents, children: filteredChildren }; };
3. 优化handleOnChange函数
添加类型约束,确保基于原始数据重新过滤:
// 假设originalEventsList是原始数据源,setEventList是状态更新函数 const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; // 仅允许指定的过滤键 if (!['city', 'price'].includes(name)) return; const result = filterBy(originalEventsList, name as keyof EventType, value); setEventList(result || originalEventsList); };
关键改进点
- 纯函数设计:每次过滤基于原始数据生成新对象,避免污染原数据。
- 类型安全:用
keyof EventType约束动态键范围,区分字符串/数字类型的过滤逻辑。 - 空值处理:输入为空时返回所有事件,提升用户体验。
- 子节点清理:过滤后自动移除无事件、无子节点的空分支。
- 类型规范:补全React事件类型,杜绝
any的使用。
内容的提问来源于stack exchange,提问作者Mina Fawzy
相关产品推荐
相关产品推荐

