最近工作上要开发鸿蒙,而我写的一些包要同时支持 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;
  }
}