【问题标题】:TypeScript JSX without React没有 React 的 TypeScript JSX
【发布时间】:2017-05-24 06:18:04
【问题描述】:

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

我在 SO 上看到了其他相关问题的答案,但没有任何内容完整或详细到足以提供任何帮助。我已经阅读了手册中的this guideJSX chapter,但并没有多大帮助。我不明白语言功能本身是如何工作的。

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

我想要的至少是一个示例,它使以下工作具有类型安全性:

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

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

我假设我至少需要声明 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>;

我用tsconfig.json 编译这个:

{
    "compilerOptions": {
        "target": "es5",
        "jsx": "react",
        "reactNamespace": "flurp"
    }
}

将鼠标悬停在 &lt;div&gt; 元素和 id="foo" 属性上,我可以看到编译器理解了我对内在元素的声明 - 到目前为止,一切都很好。

现在,要编译 &lt;Ping blurt="ya"&gt; 声明,并对 blurt 属性进行类型检查,我该怎么做? Ping 甚至应该是 class 还是 function 或其他什么?

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

我希望创建一些简单而轻量级的东西,可能类似于monkberry,但使用 JSX 语法,例如使用 &lt;If&gt;&lt;For&gt; 之类的组件,而不是内联语句。这甚至可行吗? (有没有我可以参考的现有项目?)

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

【问题讨论】:

  • 抱歉,您是否对答案进行了罚款?你能举个例子吗?

标签: typescript jsx


【解决方案1】:

确实,JSX section of the TypeScript handbook is a great resource;还没有的小伙伴快来看看吧。

如果您希望tsc 编译您的 JSX,您需要执行以下操作(非常广泛的概述):

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

这里有一个更深入的了解。另外,you might like to see a repository 或者 a working example/playground

JSX 命名空间

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

这是 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
    }
}

几点说明:

  • 如果您打算使用 HTMLElements 和 Nodes,DOM 库 (lib.dom.d.ts) 是获得更好类型检查的绝佳资源。在这个例子中,我用它来声明所有真实的 HTML 标签为有效的 Intrinsic 元素。您可以进一步扩展它,例如,通过GlobalEventHandlers 预加载onclick 之类的EventHandlers。
  • Element 是 TypeScript 在 JSX 命名空间中寻找的另一种类型。这是可选的(默认为any)。使用它来指定工厂函数的返回类型。
  • 我们可以使用这个定义创建一个简单的基于函数的组件(我已经使用Component 接口定义了一个自定义助手。
  • 对于类组件,我们需要声明JSX.ElementClass接口。有关详细信息,请参阅here

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;

}

TSX 和 tsconfig

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

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "jsx",
  }
}

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

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)

感谢JSX.Element,TypeScript 知道 JSX 的结果是 HTMLElement

【讨论】:

  • 惊人的答案——谢谢。
猜你喜欢
  • 2017-02-24
  • 2021-03-30
  • 2019-06-06
  • 2017-11-03
  • 2019-04-07
  • 1970-01-01
  • 2021-04-07
  • 2019-11-27
  • 2016-11-19
相关资源
最近更新 更多