一个让接入方自定义库的 TS 类型技巧
Jul 05, 2025
最近工作上要开发鸿蒙,而我写的一些包要同时支持 Web 和鸿蒙,部分依赖平台的接口,需要让接入方来注入。
注入的时候我们当然可以有一些类型上的约束,使得两个平台都能得到的类型提示、校验能力。
但毕竟平台有差异,有些地方就是不一样,只根据单个平台做约束会导致另一个平台类型无法通过。
举个例子,你需要让业务方注入一个 getViewContainer
的函数。
那在 Web 上该函数类型定义可能是
TS
type GetViewContainer = () => HTMLElement;
而在鸿蒙上则可能是
TS
type GetViewContainer = () => FrameNode;
你可以定义一个接口,让接入方来实现,这种方式有点啰嗦且繁琐,有点脱裤子放屁的感觉。
TS
interface BaseNode {
appendChild(child: BaseNode): void;
removeChild(child: BaseNode): void;
}
type GetViewContainer = () => BaseNode;
// Web 接入
class WebDomNode implements BaseNode {
private node: HTMLElement;
constructor(node: HTMLElement) {
this.node = node;
}
appendChild(child: WebDomNode) {
this.node.appendChild(child.node);
}
removeChild(child: WebDomNode) {
this.node.removeChild(child.node);
}
}
// 鸿蒙接入
class HarmonyNode implements BaseNode {
private node: FrameNode;
constructor(node: FrameNode) {
this.node = node;
}
appendChild(child: HarmonyNode) {
this.node.appendChild(child.node);
}
removeChild(child: HarmonyNode) {
this.node.removeChild(child.node);
}
}
我从 @tanstack/query
上学到一个技巧,利用 TS 的 interface merge 特性,定义一个 Register 类型。
TS
export interface Register {
// view: unknown;
}
export type View = Register extends {
View: Infer T
} ? T : BaseNode
使用方接入时就可以
TS
// Web
declare module "your-lib" {
interface Register {
View: HTMLElement;
}
}
// 鸿蒙
declare module "your-lib" {
interface Register {
View: FrameNode;
}
}