我已经创建了mini-jsx
。这是一个很小的库,用于使用JSX语法创建DOM元素。它是用TypeScript编写的。
现在,我想扩展它以支持功能组件。我做了很多事情,但是找不到定义适当类型注释的方法。这就是我最终得到的:
const h = <
T extends (keyof HTMLElementTagNameMap) | ((props?: P, children?: Child[]) => Node),
P extends object
>(
tag: T,
props?: T extends (keyof HTMLElementTagNameMap) ? Props<T> : P,
...children: Children[]
): T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : ReturnType<T> => {
const childArray: Child[] = [];
const flattenChildren = (child: Children): void => {
if (Array.isArray(child)) {
child.forEach(flattenChildren);
} else if (child != null && child !== true && child !== false) {
childArray.push(child);
}
};
flattenChildren(children);
if (typeof tag !== "string") {
return tag(props as P, childArray) as ReturnType<T>;
}
let ref: Ref<T> | undefined;
const node = document.createElement(tag);
if (props) {
Object.entries(props).forEach(([key, value]) => {
if (key === "ref") {
ref = value as Ref<T>;
} else if (key in node) {
type Key = keyof typeof node;
if (node[key as Key] instanceof Object && value instanceof Object) {
Object.assign(node[key as Key], value);
} else {
node[key as Key] = value as typeof node[Key];
}
} else {
node.setAttribute(key, value as string);
}
});
}
node.append(...(childArray as Node[]));
if (ref) {
ref(node);
}
return node;
};
如果我只编写类型定义,则该函数将具有2个调用签名:
type Child = number | string | Node;
interface ChildrenArray extends Array<Children> {}
type Children = boolean | null | undefined | Child | ChildrenArray;
interface Attributes<T> {
// truncated
}
type Props<T extends keyof HTMLElementTagNameMap> = Attributes<T> &
{
[K in keyof HTMLElementTagNameMap[T]]?: HTMLElementTagNameMap[T][K] extends Function
? HTMLElementTagNameMap[T][K]
: Partial<HTMLElementTagNameMap[T][K]>;
};
export function h<T extends keyof HTMLElementTagNameMap>(
tag: T,
props?: Partial<HTMLElementTagNameMap[T]>,
...children: Children[]
): HTMLElementTagNameMap[T];
export function h<T extends (props?: object, children?: Child[]) => Node>(
tag: T,
props?: Parameters<T>[0],
...children: Children[]
): ReturnType<T>;
当前我遇到以下错误:
h.ts:452:82 - error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.
Type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 100 more ... | ((props?: P, children?: Child[]) => Node)' is not assignable to type '(...args: any) => any'.
Type '"object"' is not assignable to type '(...args: any) => any'.
452 ): T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : ReturnType<T> => {
~
h.ts:463:5 - error TS2322: Type 'ReturnType<T>' is not assignable to type 'T extends "object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 99 more ... | "wbr" ? HTMLElementTagNameMap[T] : ReturnType<...>'.
463 return tag(props as P, childArray) as ReturnType<T>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
h.ts:463:12 - error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 100 more ... | ((props?: P, children?: Child[]) => Node)' has no compatible call signatures.
463 return tag(props as P, childArray) as ReturnType<T>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~
h.ts:463:54 - error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.
463 return tag(props as P, childArray) as ReturnType<T>;
~
h.ts:465:16 - error TS2344: Type 'T' does not satisfy the constraint '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 99 more ... | "wbr"'.
Type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 100 more ... | ((props?: P, children?: Child[]) => Node)' is not assignable to type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 99 more ... | "wbr"'.
Type '(props?: P, children?: Child[]) => Node' is not assignable to type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 99 more ... | "wbr"'.
Type '(props?: P, children?: Child[]) => Node' is not assignable to type '"wbr"'.
Type 'T' is not assignable to type '"wbr"'.
Type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 100 more ... | ((props?: P, children?: Child[]) => Node)' is not assignable to type '"wbr"'.
Type '"object"' is not assignable to type '"wbr"'.
465 let ref: Ref<T> | undefined;
~
h.ts:466:39 - error TS2345: Argument of type 'T' is not assignable to parameter of type 'string'.
Type '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 100 more ... | ((props?: P, children?: Child[]) => Node)' is not assignable to type 'string'.
Type '(props?: P, children?: Child[]) => Node' is not assignable to type 'string'.
466 const node = document.createElement(tag);
~~~
h.ts:470:28 - error TS2344: Type 'T' does not satisfy the constraint '"object" | "link" | "small" | "sub" | "sup" | "track" | "progress" | "a" | "abbr" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdi" | ... 99 more ... | "wbr"'.
Type 'T' is not assignable to type '"wbr"'.
470 ref = value as Ref<T>;
我创建了以下工作测试。我也一直在使用这些来验证TypeScript是否能够识别确切的类型。
test("HTML without JSX or props", () => {
const li = h("li");
expect(li).toBeInstanceOf(HTMLLIElement);
});
test("Function without JSX with props", () => {
function Div({ bar }: { bar: string }): HTMLDivElement {
return <div className={bar} /> as HTMLDivElement;
}
const div = h(Div, { bar: "test" });
expect(div).toBeInstanceOf(HTMLDivElement);
expect(div.className).toBe("test");
});
test("Function without JSX or props", () => {
function Div(): HTMLDivElement {
return <div /> as HTMLDivElement;
}
const div = h(Div);
expect(div).toBeInstanceOf(HTMLDivElement);
});
如何解决这些类型?还是我最好将代码转换为JavaScript,然后将类型分成.d.ts
文件?