使JSX Element TypeScript类型成为动态

时间:2019-07-29 12:59:20

标签: typescript jsx

我创建了一个很小的TypeScript库,该库将JSX渲染到DOM节点。

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace JSX {
    type Element = HTMLElement;

    type IntrinsicElements = {
      [K in keyof HTMLElementTagNameMap]: Partial<HTMLElementTagNameMap[K]>;
    };
  }
}

interface Props {
  [prop: string]: any;
}

type Child = boolean | number | string | Node | Children;

// This is a workaround for https://github.com/microsoft/TypeScript/issues/6230
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Children extends Array<Child> {}

function appendChildren(node: Node, children: Child): void {
  if (Array.isArray(children)) {
    children.forEach(child => {
      appendChildren(node, child);
    });
  } else if (children !== null && children !== true && children !== false) {
    node.appendChild(
      typeof children === 'string' || typeof children === 'number'
        ? document.createTextNode(`${children}`)
        : children,
    );
  }
}

/**
 * Create a DOM node using a JSX compatible function.
 *
 * @param tag The HTML tag name of the DOM node to create, or a function that returns a DOM node.
 * @param props Properties to assign to the DOM node or props to pass to the tag function.
 * @param children DOM nodes to append to the newly created DOM node. These may also be strings or
 *   numbers. If a boolean or `null` is passed, the value is ignored.
 *
 * @returns The created DOM node.
 */
export default <T extends keyof HTMLElementTagNameMap>(
  tag: T | ((props: Props) => HTMLElementTagNameMap[T]),
  props: Props = {},
  ...children: Children
): HTMLElementTagNameMap[T] => {
  const node =
    typeof tag === 'string' ? Object.assign(document.createElement(tag), props) : tag(props);
  appendChildren(node, children);
  return node;
};

除了TypeScript无法识别返回的DOM节点之外,这还算不错。

例如:

/* @jsx h */
import h from '.';

// According to TypeScript, `a` is an `HTMLElement`
const a = <div />

// According to TypeScript, `b` is an `HTMLDivElement`
const b = h('div')

// This works, but it would be nice if the type casting could be avoided
const c = <div /> as HTMLDivElement

// TypeScript accepts this, even though it is wrong
const d = <div /> as HTMLButtonElement

如何使TypeScript识别JSX元素的类型?

0 个答案:

没有答案