不带 React 的 TypeScript JSX

min*_*.dk 12 jsx typescript

我想在 TypeScript 中使用 JSX 语法,但不想使用 React。

我在这里看到了其他相关问题的答案,但没有任何内容足够完整或详细,无法提供任何帮助。我已经阅读了本指南和手册中的JSX 章节,但没有多大帮助。我不明白语言功能本身是如何工作的。

我尝试检查 React 声明,但它对我来说太大了,无法吸收 - 我需要一个最小的、有效的示例来演示类型检查的元素、组件和属性。(它不需要工厂函数的有效实现,我最感兴趣的是声明。)

我至少想要一个能够实现以下工作且具有类型安全性的示例:

var el = <Ping blurt="ya"></Ping>;

var div = <div id="foo">Hello JSX! {el}</div>;
Run Code Online (Sandbox Code Playgroud)

JSX.IntrinsicElements我想我至少需要声明createElement()某种工厂函数,但这就是我所得到的:

declare module JSX {
    interface IntrinsicElements {
        div: flurp.VNode<HTMLDivElement>;
    }
}

module flurp {

    export interface VNode<E extends Element> {
        id: string
    }

    export function createElement<T extends Element>(type: string, props?: any, ...children: (VNode<Element>|string)[]): VNode<Element> {
        return {
            id: props["id"]
        };
    }
}

class Ping {
    // ???
}

var el = <Ping blurt="ya"></Ping>;

var div = <div id="foo">Hello JSX! {el}</div>;
Run Code Online (Sandbox Code Playgroud)

我用这样的方式编译它tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "jsx": "react",
        "reactNamespace": "flurp"
    }
}
Run Code Online (Sandbox Code Playgroud)

将鼠标悬停在<div>元素和id="foo"属性上,我可以看到编译器理解我的内在元素声明 - 到目前为止,一切顺利。

现在,要<Ping blurt="ya">通过属性的类型检查来编译声明blurt,我该怎么办?甚至应该Ping是 aclass还是可能是 afunction或者什么?

理想情况下,元素和组件应该具有某种共同的祖先,因此我可以拥有一组适用于两者的共同属性。

我希望创建一些简单且轻量级的东西,可能类似于 Monkberry 但使用 JSX 语法,例如使用像<If>and 之类的组件<For>而不是内联语句。这可行吗?(有没有类似的现有项目可供我参考?)

请,如果有人可以提供一个如何使用语言功能本身的有效的、最小的示例,那将是一个巨大的帮助!

Con*_*Low 15

事实上,TypeScript 手册的 JSX 部分是一个很好的资源;对于那些还没有看过的人,请检查一下。

如果您想tsc编译 JSX,您将需要执行以下操作(极其广泛的概述):

  1. 在命名空间中声明接口IntrinsicElements/类型JSX(启用类型安全)。
  2. 创建一个 JSX 工厂函数。
  3. 配置您的消费应用程序的tsconfig以使用"react"(或另一个 jsx 选项)代码生成并指向您的 JSX Factory(使用该"jsxFactory"选项)。
  4. 将 JSX 工厂函数导入到.tsx文件中。

这是对此进行更深入的了解。另外,您可能想查看存储库工作示例/游乐场

JSX 命名空间

在这里,您告诉 TypeScript 如何解释您的 JSX 代码。至少,您需要声明IntrinsicElements,但您还可以声明其他类型,这些类型将为您提供更好的类型提示、启用组件功能,并通常改进/调整 TypeScript 对 JSX 的理解方式。

以下是 JSX 命名空间的声明示例:

/// <reference lib="DOM" />

declare namespace JSX {
    // The return type of our JSX Factory: this could be anything
    type Element = HTMLElement;

    // IntrinsicElementMap grabs all the standard HTML tags in the TS DOM lib.
    interface IntrinsicElements extends IntrinsicElementMap { }


    // The following are custom types, not part of TS's known JSX namespace:
    type IntrinsicElementMap = {
        [K in keyof HTMLElementTagNameMap]: {
            [k: string]: any
        }
    }

    interface Component {
        (properties?: { [key: string]: any }, children?: Node[]): Node
    }
}
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

  • 如果您打算使用HTMLElements 和Nodes,DOM 库 ( lib.dom.d.ts ) 是一个很好的资源,可以帮助您更好地进行类型检查。在此示例中,我使用它来将所有真实的 HTML 标记声明为有效的内部元素。您可以进一步扩展它,例如,预加载viaEventHandler之类的。onclickGlobalEventHandlers
  • Element是 TypeScript 在 JSX 命名空间中查找的另一种类型。这是可选的(默认为any)。使用它来指定工厂函数的返回类型。
  • 我们可以使用此定义创建一个简单的基于函数的组件(我已经使用该Component接口定义了一个自定义帮助器)。
  • 对于类组件,我们需要声明JSX.ElementClass接口。请参阅此处了解更多详细信息。

JSX 工厂函数

命名JSX空间帮助我们定义如何在代码中使用 JSX,但我们仍然需要实现一个可以处理tsc. 我们的工厂函数应该遵循表格(tag: string, properties: { [k: string]: any }, ...children: any[]): any,或者更具体的东西。这是一个启用功能组件的示例(注意它又大又丑):

function jsx(tag: JSX.Tag | JSX.Component, 
             attributes: { [key: string]: any } | null, 
             ...children: Node[]) 
{

    if (typeof tag === 'function') {
        return tag(attributes ?? {}, children);
    }
    type Tag = typeof tag;
    const element: HTMLElementTagNameMap[Tag] = document.createElement(tag);

    // Assign attributes:
    let map = (attributes ?? {});
    let prop: keyof typeof map;
    for (prop of (Object.keys(map) as any)) {

        // Extract values:
        prop = prop.toString();
        const value = map[prop] as any;
        const anyReference = element as any;
        if (typeof anyReference[prop] === 'undefined') {
            // As a fallback, attempt to set an attribute:
            element.setAttribute(prop, value);
        } else {
            anyReference[prop] = value;
        }
    }

    // append children
    for (let child of children) {
        if (typeof child === 'string') {
            element.innerText += child;
            continue;
        }
        if (Array.isArray(child)) {
            element.append(...child);
            continue;
        }
        element.appendChild(child);
    }
    return element;

}
Run Code Online (Sandbox Code Playgroud)

TSX 和 tsconfig

现在,剩下的就是使用我们的jsx函数了。我们的 tsconfig 至少应该配置以下内容:

{
  "compilerOptions": {
    "jsx": "react", // or react-jsx, etc.
    "jsxFactory": "jsx",
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,我们可以在任何文件中编写 JSX .tsx,只要我们导入jsxFactory

import jsx from './jsxFactory';

function Ping({ blurt }: { blurt: string }) {
    return <div>{blurt}</div>
}

var el = <Ping blurt="ya"></Ping>;

// var div: HTMLElement
var div = <div id="foo">Hello JSX! {el}</div>;

document.body.appendChild(div)
Run Code Online (Sandbox Code Playgroud)

感谢JSX.ElementTypeScript 知道 JSX 的结果是一个HTMLElement.