条件返回类型

时间:2019-09-24 08:20:59

标签: typescript

我已经创建了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个调用签名:

  • 将接受HTML标记名称和部分DOM节点,然后返回映射到给定标记名称的DOM节点;
  • 另一个将接受一个接受props和child并返回DOM节点的函数,并仅使用给定的props和平展的child调用该函数。
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文件?

0 个答案:

没有答案