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

如何让TypeScript的multipleSelect函数保留querySelector精确类型推导?

实现带类型推导的多选择器元素查询函数

我编写了一个multipleSelect函数,用于根据传入的选择器对象返回对应DOM元素,但目前所有返回元素的类型都统一为Element,无法像原生querySelector那样自动推导具体的元素类型。

原函数代码

export default function multipleSelect<T extends string>(
  parent: ParentNode,
  selectors: Record<T, string>
) {
  const selected = {} as {
    [key in keyof typeof selectors]: Element
  }
  for (const [key, selector] of Object.entries(selectors) as [T, string][]) {
    const element = parent.querySelector(selector)
    if (!element) throw new Error(`parent doesn't contain '${key}' element`)
    selected[key] = element
  }
  return selected
}

使用示例

const app = document.querySelector('.app')!
const elements = multipleSelect(app, {
  title: 'h3',
  list: 'ul',
  canvas: 'canvas',
  circle: 'circle',
  currentLi: 'ul>li.current',
})

当前返回类型

const elements: {
  title: Element;
  list: Element;
  canvas: Element;
  circle: Element;
  currentLi: Element;
}

期望的返回类型

希望能保留querySelector的类型推导特性,让返回类型自动匹配选择器对应的具体元素:

const elements: {
    title: HTMLHeadingElement;
    list: HTMLUListElement;
    canvas: HTMLCanvasElement;
    circle: SVGCircleElement;
    currentLi: Element;
}

解决方案

要实现这个需求,我们可以利用TypeScript内置的元素标签类型映射,并通过模板字面量类型提取选择器中的标签名,从而推导对应的元素类型。修改后的函数如下:

// 合并HTML和SVG的标签-元素类型映射
type ElementTagNameMap = HTMLElementTagNameMap & SVGElementTagNameMap;

// 从选择器字符串中提取第一个标签名(支持处理带层级/类名的选择器)
type ExtractTagName<S extends string> = 
  S extends `${infer TagName}${string}` 
    ? TagName extends keyof ElementTagNameMap 
      ? TagName 
      : never 
    : never;

// 根据选择器字符串推导对应的元素类型
type SelectorToElement<S extends string> = 
  ExtractTagName<S> extends keyof ElementTagNameMap 
    ? ElementTagNameMap[ExtractTagName<S>] 
    : Element;

export default function multipleSelect<T extends Record<string, string>>(
  parent: ParentNode,
  selectors: T
) {
  const selected = {} as {
    [K in keyof T]: SelectorToElement<T[K]>
  };

  for (const [key, selector] of Object.entries(selectors) as [keyof T, string][]) {
    const element = parent.querySelector(selector);
    if (!element) throw new Error(`parent doesn't contain '${String(key)}' element`);
    selected[key] = element as SelectorToElement<T[key]>;
  }

  return selected;
}

代码说明

  1. ElementTagNameMap:合并TypeScript内置的HTML和SVG标签-元素类型映射表,覆盖绝大多数常用元素的类型对应关系。
  2. ExtractTagName:通过模板字面量类型从选择器字符串中提取第一个标签名(比如从"ul>li.current"中提取"ul"),仅保留映射表中存在的标签名。
  3. SelectorToElement:根据提取到的有效标签名,从映射表中获取对应的具体元素类型;若选择器无法提取有效标签名,则返回基础Element类型。
  4. 泛型与类型断言:调整泛型定义以支持遍历选择器对象的键值对,通过类型断言确保DOM元素赋值时的类型匹配。

验证结果

使用原示例调用修改后的函数,elements的类型将自动推导为预期的具体元素类型,完全符合需求。


内容的提问来源于stack exchange,提问作者Yukulélélé

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:35:25