TypeScript 无法扩展已定义元素的JSX属性

hfwmuf9z  于 4个月前  发布在  TypeScript
关注(0)|答案(6)|浏览(61)

Bug报告

我们刚刚发布了Preact Signals,它是一种既可以用于Preact又可以用于React的附加组件,允许您将可观察对象直接传递到JSX中。作为其中的一部分,我们需要扩展现有的JSX定义,以允许我们也传递可观察类型,而不仅仅是现有的类型。但是我无法做到这一点。我一直收到一个类型错误。

🔎搜索词

JSX,声明合并,React,Preact,信号,IntrinsicElements

🕗版本和回归信息

  • 这是我在每个版本中尝试的行为,我也查阅了关于JSX + 声明合并的常见问题解答条目

⏯ Playground链接

带有相关代码的Playground链接

💻代码

import * as React from "react"

// This and the JSX additions live in a library
interface Signal<T> {
    value: T
}

declare global {
    namespace JSX {
        interface IntrinsicElements {
            // Doesn't work
            input:
                | React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>,HTMLInputElement>
                | Signal<string>
        }
    }
}

// This represents app code the user writes
const foo: Signal<string> = {
    value: "foo"
}

const a = <input value={foo} />

🙁实际行为

Signal作为value传递时出现类型错误

const foo = { value: "foo" };
const a = <input value={foo} />

🙂预期行为

Signal作为value传递应该可以工作

const foo = { value: "foo" };
const a = <input value={foo} />
6l7fqoea

6l7fqoea1#

请注意,您不必使用(并且我认为不应该)全局JSX命名空间。
您可以通过配置jsxImportSource来解决这个问题,这将带来自己的本地JSX命名空间,不会污染全局命名空间。我看到您已经在Preact中利用了本地JSX命名空间。我认为您应该将操纵后的JSXInternal与来自您的@preact/signals包的运行时工厂一起重新导出,人们应该在他们的tsconfig.json中指向该包。

wztqucjr

wztqucjr2#

是的,在Preact中我们使用本地JSX命名空间。告诉用户指向另一个包的JSX是可以的,但我担心这种方法一旦有其他包也想扩展类型就会出问题。今天只有Signals,但我可以想象例如有一个RxJS绑定的原生适配器。所以我们需要一种方法来结合所有它们。

x0fgdtte

x0fgdtte3#

告诉用户指向另一个包的JSX将起作用,但我担心这种方法一旦有另一个包也想扩展类型就会失效。

这是一个有效的担忧,但仍然可能通过类型的组合来处理这个问题。请注意,这至少会在某个地方引用"组合"模块,并且它会使其可发现 - 而不是依赖于隐式扩展。它也不需要引入任何新的语言结构 - 目前这些接口的行为与通常的接口合并方式相同,但当然,这并不适合您的用例。这种行为不能改变 - 因此必须引入一些新的东西来解决这个问题。

总的来说,接口合并的问题在于它需要对项目有完整的了解才能正确工作 - 因此从本质上讲,它会降低项目的启动时间等。这只是我的两分钱观点,但显式依赖>>>隐式依赖,正是因为这个原因。

gjmwrych

gjmwrych4#

我担心这种方法一旦有另一个包也想要扩展类型,就会失效。在一般情况下,如何使这种情况通用化,除了已经提出的上层方案之外,还有其他方法吗?如果 Signal2 也存在并且想要使事情正常工作,会发生什么?

TS 应该使用 Union 还是 Intersection 来合并这两个类型?为什么/如何?这非常不清楚。即使我们有一些关键字,如 merge,这样你就可以使用 input: merge | Signal<string> 来引用你正在覆盖的属性,但你仍然会遇到声明顺序问题,因为在同一属性槽中重复出现的 merge 可能以具有顺序依赖性的方式表现出来(例如 input: Foo<merge> )

qqrboqgw

qqrboqgw5#

我还想更好地理解一件事,那就是Preact是如何让React起作用的。通常,“这里的类型应该做什么”的问题可以通过更精确地询问运行时如何工作来更容易地回答。

ff29svar

ff29svar6#

TS应该使用Union还是Intersection来合并这两种类型?为什么/如何?这非常不清楚。即使我们有一些关键字,如merge,以便你可以写入input: merge | Signal来引用你正在覆盖的属性,然后你会遇到声明顺序问题,因为在同一个属性槽中重复出现的merge可能会以具有顺序依赖性的方式表现出来(例如:input: Foo)
这是一个很好的观点,我最初创建这个问题时没有考虑到这一点。我同意你的观点,这里的歧义太多了,很容易陷入一个无底洞。听起来显式地组合消费代码库中的类型是正确的方法。
我还想更好地理解Preact是如何让React起作用的。通常,“这里应该是什么类型”的问题可以通过更精确地询问运行时是如何工作的来更容易回答的。
对于Preact来说,显然要容易得多,因为我们提供了官方的方法来接入渲染器。对于React来说,这更棘手,涉及到侵入内部。我认为从一个类型系统中推导出类型几乎是不可能的,因为它被一堆抽象所掩盖。

相关问题