如何修复Puppeteer+TypeScript自定义innerText获取方法的编译错误
getInnerText函数的TypeScript编译错误 问题背景
我原本用这段代码获取HTML元素的innerText:
import puppeteer, { Page } from 'puppeteer'; const main = async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); const book_title = await page.$eval( 'h1.Text__title1', (el: HTMLHeadingElement) => el.innerText ); }
为了复用逻辑,我写了getInnerText方法:
async function getInnerText<T extends HTMLElement>( page: Page, selector: string ): Promise<string | null> { const innerText = await page.$eval( selector, (el: T) => el.innerText ) as string | null; return innerText; }
调用方式:
const book_title = await getInnerText<HTMLHeadingElement>(page, 'h1.Text__title1')
但VS Code里(el: T) => el.innerText处出现编译错误:
Argument of type '(el: T) => string' is not assignable to parameter of type 'string | EvaluateFuncWith<Element, []>'.
Type '(el: T) => string' is not assignable to type 'EvaluateFuncWith<Element, []>'.
Types of parameters 'el' and 'params_0' are incompatible.
Type 'Element' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'Element'.
错误原因
TypeScript类型检查器认为:虽然你声明了T extends HTMLElement,但page.$eval的回调默认接收的参数类型是Element,而泛型T作为HTMLElement的子类型,约束比Element更严格,Element无法直接赋值给T,因此出现类型不兼容的报错。
修复方案
方案1:回调内对元素做类型断言
直接在回调中把Element断言为T,明确告知TypeScript该元素符合泛型T的类型:
async function getInnerText<T extends HTMLElement>( page: Page, selector: string ): Promise<string | null> { const innerText = await page.$eval( selector, (el: Element) => (el as T).innerText ); return innerText ?? null; }
方案2:用page.$ + evaluate替代page.$eval
先通过page.$获取元素句柄,再调用evaluate处理,这种方式更直观处理元素不存在的场景:
async function getInnerText<T extends HTMLElement>( page: Page, selector: string ): Promise<string | null> { const elementHandle = await page.$(selector); if (!elementHandle) return null; const innerText = await elementHandle.evaluate((el: T) => el.innerText); await elementHandle.dispose(); // 释放资源 return innerText; }
方案3:简化泛型约束
让泛型T直接继承Element,利用TypeScript的类型兼容性简化约束:
async function getInnerText<T extends Element>( page: Page, selector: string ): Promise<string | null> { const innerText = await page.$eval( selector, (el: T) => el.innerText ); return innerText ?? null; }
调用时依然可以传入HTMLHeadingElement作为泛型参数,因为它是Element的子类型,完全符合约束要求。
说明
三种方案均可解决类型不兼容问题:方案1最简洁,适合快速修复;方案2在需要处理元素不存在的场景时逻辑更清晰;方案3简化了泛型约束的写法,降低类型复杂度,可根据实际需求选择。
内容的提问来源于stack exchange,提问作者Shashank

