使用TypeScript为DOM元素创建可扩展的自定义选择器与方法
在前端开发中,原生的DOM选择器(如document.querySelector、document.querySelectorAll)已经能满足大部分基础需求,但在复杂项目中,我们常常需要重复编写相似的选择逻辑,或者需要针对特定业务场景定制选择规则。使用TypeScript封装一套可扩展的自定义选择器与方法,既能提升代码复用性,又能借助类型系统减少运行时错误,非常适合中大型项目的开发维护。
核心设计思路
自定义选择器体系的核心目标是:统一选择入口、支持规则扩展、保证类型安全。我们可以将选择逻辑拆解为三个部分:
- 基础选择器:封装原生DOM选择能力,提供类型安全的返回值
- 规则注册器:允许业务方动态添加自定义选择规则,无需修改核心代码
- 扩展方法:基于选择器封装常用的DOM操作逻辑,减少重复代码
基础选择器实现
首先我们定义基础的选择器类型,确保选择操作返回的元素类型符合预期,避免后续使用时出现类型错误。
// 定义选择器选项类型
interface SelectorOptions {
// 是否在找不到元素时抛出错误,默认false
throwIfNotFound?: boolean;
// 选择器的额外上下文,默认是document
context?: Document | HTMLElement;
}
// 基础选择器类
class CustomSelector {
/**
* 选择单个DOM元素
* @param selector 选择器字符串,支持原生CSS选择器或自定义规则前缀(如@data-id=xxx)
* @param options 选择配置项
* @returns 选中的元素或null
*/
static query<T extends HTMLElement = HTMLElement>(
selector: string,
options: SelectorOptions = {}
): T | null {
const { throwIfNotFound = false, context = document } = options;
// 优先匹配自定义规则
const customElement = this.matchCustomRule<T>(selector, context);
if (customElement !== undefined) {
return customElement;
}
// 匹配原生选择器
const element = context.querySelector<T>(selector);
if (!element && throwIfNotFound) {
throw new Error(`未找到匹配选择器「${selector}」的元素`);
}
return element;
}
/**
* 选择多个DOM元素
* @param selector 选择器字符串
* @param options 选择配置项
* @returns 选中的元素数组
*/
static queryAll<T extends HTMLElement = HTMLElement>(
selector: string,
options: SelectorOptions = {}
): T[] {
const { context = document } = options;
// 优先匹配自定义规则
const customElements = this.matchCustomRuleAll<T>(selector, context);
if (customElements !== undefined) {
return customElements;
}
// 匹配原生选择器
const elements = Array.from(context.querySelectorAll<T>(selector));
return elements;
}
// 自定义规则匹配逻辑,后续补充
private static matchCustomRule<T extends HTMLElement>(
selector: string,
context: Document | HTMLElement
): T | null | undefined {
return undefined;
}
// 自定义规则批量匹配逻辑,后续补充
private static matchCustomRuleAll<T extends HTMLElement>(
selector: string,
context: Document | HTMLElement
): T[] | undefined {
return undefined;
}
}自定义规则扩展机制
为了让选择器具备可扩展性,我们需要实现一个规则注册器,允许开发者动态添加自定义选择规则,比如按data-id属性选择、按元素自定义属性选择等场景。
// 自定义规则类型定义
type CustomRuleMatcher = (
selector: string,
context: Document | HTMLElement
) => HTMLElement | HTMLElement[] | null | undefined;
class SelectorRuleRegistry {
// 存储单个元素匹配规则,key为规则前缀
private static singleRules: Map<string, CustomRuleMatcher> = new Map();
// 存储多个元素匹配规则,key为规则前缀
private static multipleRules: Map<string, CustomRuleMatcher> = new Map();
/**
* 注册单个元素匹配规则
* @param prefix 规则前缀,比如@data-id,选择时写@data-id=123就会匹配该规则
* @param matcher 匹配函数
*/
static registerSingleRule(prefix: string, matcher: CustomRuleMatcher): void {
this.singleRules.set(prefix, matcher);
}
/**
* 注册多个元素匹配规则
* @param prefix 规则前缀
* @param matcher 匹配函数
*/
static registerMultipleRule(prefix: string, matcher: CustomRuleMatcher): void {
this.multipleRules.set(prefix, matcher);
}
/**
* 匹配单个元素的自定义规则
*/
static matchSingle(
selector: string,
context: Document | HTMLElement
): HTMLElement | null | undefined {
for (const [prefix, matcher] of this.singleRules.entries()) {
if (selector.startsWith(prefix)) {
const result = matcher(selector, context);
if (result instanceof HTMLElement) {
return result;
}
return undefined;
}
}
return undefined;
}
/**
* 匹配多个元素的自定义规则
*/
static matchMultiple(
selector: string,
context: Document | HTMLElement
): HTMLElement[] | undefined {
for (const [prefix, matcher] of this.multipleRules.entries()) {
if (selector.startsWith(prefix)) {
const result = matcher(selector, context);
if (Array.isArray(result)) {
return result;
}
return undefined;
}
}
return undefined;
}
}接下来我们补全CustomSelector中的自定义规则匹配逻辑,对接规则注册器:
class CustomSelector {
// ... 之前的代码保持不变
private static matchCustomRule<T extends HTMLElement>(
selector: string,
context: Document | HTMLElement
): T | null | undefined {
const result = SelectorRuleRegistry.matchSingle(selector, context);
if (result !== undefined) {
return result as T;
}
return undefined;
}
private static matchCustomRuleAll<T extends HTMLElement>(
selector: string,
context: Document | HTMLElement
): T[] | undefined {
const result = SelectorRuleRegistry.matchMultiple(selector, context);
if (result !== undefined) {
return result as T[];
}
return undefined;
}
}注册常用自定义规则示例
我们可以预注册一些常用的自定义规则,减少业务中的重复配置。比如按data-id属性选择元素的规则:
// 注册按data-id选择单个元素的规则,使用前缀 @data-id=
SelectorRuleRegistry.registerSingleRule('@data-id=', (selector, context) => {
// 提取选择器中的data-id值,比如@data-id=123,提取出123
const idValue = selector.replace('@data-id=', '');
if (!idValue) return null;
// 在上下文中查找[data-id="xxx"]的元素
return context.querySelector(`[data-id="${idValue}"]`);
});
// 注册按data-id选择多个元素的规则
SelectorRuleRegistry.registerMultipleRule('@data-id-all=', (selector, context) => {
const idValue = selector.replace('@data-id-all=', '');
if (!idValue) return [];
return Array.from(context.querySelectorAll(`[data-id="${idValue}"]`));
});
// 注册按自定义属性data-role选择元素的规则,前缀@role=
SelectorRuleRegistry.registerMultipleRule('@role=', (selector, context) => {
const roleValue = selector.replace('@role=', '');
if (!roleValue) return [];
return Array.from(context.querySelectorAll(`[data-role="${roleValue}"]`));
});扩展常用DOM操作方法
基于自定义选择器,我们可以进一步封装常用的DOM操作方法,让业务代码更简洁。比如封装显示/隐藏元素、获取表单值等方法:
class DOMUtils {
/**
* 显示指定选择器的元素
* @param selector 选择器字符串
* @param options 选择配置
*/
static show(
selector: string,
options?: SelectorOptions
): void {
const element = CustomSelector.query(selector, options);
if (element) {
element.style.display = '';
}
}
/**
* 隐藏指定选择器的元素
* @param selector 选择器字符串
* @param options 选择配置
*/
static hide(
selector: string,
options?: SelectorOptions
): void {
const element = CustomSelector.query(selector, options);
if (element) {
element.style.display = 'none';
}
}
/**
* 获取表单元素的值,支持input、select、textarea
* @param selector 选择器字符串
* @param options 选择配置
* @returns 表单值或null
*/
static getFormValue(
selector: string,
options?: SelectorOptions
): string | string[] | null {
const element = CustomSelector.query(selector, options);
if (!element) return null;
// 处理input元素
if (element instanceof HTMLInputElement) {
if (element.type === 'checkbox' || element.type === 'radio') {
return element.checked ? element.value : null;
}
return element.value;
}
// 处理select元素
if (element instanceof HTMLSelectElement) {
if (element.multiple) {
return Array.from(element.selectedOptions).map(opt => opt.value);
}
return element.value;
}
// 处理textarea元素
if (element instanceof HTMLTextAreaElement) {
return element.value;
}
return null;
}
}实际使用示例
完成上述封装后,业务中使用自定义选择器和扩展方法会非常简洁,同时具备类型提示:
// 使用原生选择器选择元素
const header = CustomSelector.query<HTMLElement>('.page-header');
if (header) {
header.style.backgroundColor = '#f5f5f5';
}
// 使用自定义规则选择元素,按data-id选择
const userCard = CustomSelector.query('@data-id=1001', { throwIfNotFound: true });
if (userCard) {
console.log('找到用户卡片', userCard);
}
// 使用自定义规则批量选择元素
const allButtons = CustomSelector.queryAll('@role=btn');
allButtons.forEach(btn => {
btn.addEventListener('click', () => console.log('按钮被点击'));
});
// 使用扩展方法操作元素
DOMUtils.hide('@data-id=loading-mask');
DOMUtils.show('.content-wrapper');
// 获取表单值
const username = DOMUtils.getFormValue('input[name="username"]');
console.log('用户名:', username);方案优势总结
- 可扩展性强:通过规则注册器可以随时添加新的选择规则,无需修改核心选择器代码,符合开闭原则
- 类型安全:TypeScript的类型定义让选择操作的返回值类型明确,减少类型断言和运行时错误
- 代码复用:统一的入口和扩展方法避免了重复的选择逻辑和DOM操作代码,降低维护成本
- 兼容性好:自定义规则优先匹配,原生选择器作为兜底,完全兼容现有原生选择逻辑,迁移成本低
TypeScriptDOM选择器自定义选择器前端开发可扩展封装 本作品最后修改时间:2026-05-22 14:22:45