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

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[];
}

现有代码问题分析

  1. 污染原始数据:直接修改传入的data对象,导致后续过滤无法基于初始数据重新计算。
  2. 类型不安全:event[name]无类型约束,且price是数字类型,直接调用字符串方法match会报错。
  3. 过滤逻辑缺陷:空输入时会过滤掉所有事件,未区分字符串(city)和数字(price)的匹配逻辑。
  4. 子节点处理粗糙:map后可能返回undefined,未过滤无效子节点。
  5. 事件类型缺失: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 05:40:53