microsoft / microsoft/TypeScript

JSX types should automatically pick setter types instead of getter types.

未关闭
#61,341 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
Awaiting More Feedback Suggestion
主要语言
Go
星标
111k
派生
14.3k
平均合并
2 天 4 小时
30 天内合并 PR
132

描述

### 🔍 Search Terms

"typescript github jsx setter type"

Related:

- https://github.com/microsoft/TypeScript/issues/60162

(however that issue is for mapped types in general, while this one is for fixing how TypeScript's internal mechanism reads types passed into JSX (UppercaseComponents) or JSX.IntrinsicElements)

### ✅ Viability Checklist

- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [x] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [x] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

When using a class definition for a JSX type, the JSX prop types will type check *against the getter, not the setter* of a class property.

### 📃 Motivating Example

Although JSX can require or not require certain values, JSX is effectively a language feature for *passing* or *setting* values.

They should be treated mode like optional parameters `?` is specified, and should otherwise look at *setter* types to achieve the semantics they truly align with.

Here's an example:

```ts
class MyClass {
get foo(): number {...}
set foo(value: string | number) {...}
}

function Component() {
// This is valid:
return // TYPE ERROR ("string not assignable to number")
}
```

We get a type error, but `"123"` is a valid value because the `foo` setter accepts string values based on its type definition.

But this is the crux: we're not *getting* the class value, we're *setting* it, for all intents and purposes. It doesn't matter if under the hood we're actually *creating a vdom object*, as that is merely an implementation detail, while the language itself is really determing other use cases:

- we're *passing* values to a function
- we're *setting* values on an object
- we're *assigning* values onto an object

As an example, this is especially true with [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), in situations like this,

```js
return
```

where we are *setting* a property on the DOM element instance.

The problem is that TypeScript is type checking JSX props based on the *getter* type of a property, but in fact they are effectively *setters* that are meant to *pass* values to their destination, the user is not *getting* a value from the JSX.

An example Custom Element definition:

```js
class SomeEl extends HTMLElement {
#someProp = 123 // type is 'number'

// returns 'number'
get someProp() { return this.#someProp }

// accepts 'string | number'
set someProp(val: string | number) { this.#someProp = Number(val) }
}

customElements.define('some-custom-element', SomeEl)
```

To represent this, TypeScript needs to effectively treat JSX props as similar to function parameters instead of as an object type, allowing some "parameters" to be optional, and preferring *setter* types when those exist.

It is possible someone will assign `SomeEl` into `JSX.IntrinsicElements` for JSX type checking,

```ts
declare module 'some-jsx-framework' {
namespace JSX {
interface IntrinsicElements {
'some-custom-element': SomeEl
}
}
}
```

so the system would need to take in the object/class type, and map that to its internal handling of JSX which would be more like parameter, having preferred any *setter* types when they exist.

This [playground example](https://www.typescriptlang.org/play/?ssl=15&ssc=31&pln=15&pc=32#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wChSkAPSWONAGxQGcm4BZATwFF64qYkAOwAmrABIAVNgBkeSEEPgBvUnDVwAxJggQ4AXjgBGAEwBmcurgBzJFh0AKAJQAuOIICuIAEZIocJXBEMO5QgnAwABbATAB0WjpwAL4W6ky2cNoQ9gBuKPSuAEQmpgVwAD5unj5Qjv7hUbHxugYAclW+OXm1yarqCpEQwp35cEwwUMCCVi5w2RDAwv7JPcJIDChEcCCD7vRIBEToMPj+vWqCKApMYOj7AFIAygAap5aWkwJQmLdwAJKC40mTGAaDkCgBrBUb2hBBAHAAtEh6PhXJweGc3slLMtyJh3IIMMAIGFOABhXBgJyvdRBEJhAA8cMRvEyeiURTMBUScAA9AA+Ug9GAcMD7TgABRBAGt6RIADRwADSSA4TD5+n8AG1FXBJnApSqIJg4BI4MwlXxKAIRKxlaq4AB+C2uQRIbK+AC6rgk2o9ySAA) show a type error for a valid value.

### 💻 Use Cases

1. This will improve JSX type definitions.
2. Current approaches are limited to getter types, and JSX cannot fully represent desired types.
3. Workarounds include declaring fake properties with conventional syntax such as `__jsx__foo: string | number` from which a mapped type can map to a JSX type definition. Example:
```ts
const MyClass = class MyClass {
get foo(): number {...}
set foo(value: string | number) {...}
/** End users: do not use this, this is for JSX types only. */
__jsx__foo: string | number
}

// This is a special mapped type that maps `__jsx__*` properties for JSX types.
type MyClass = MySpecialMappedType

function Component() {
return // fixed
}
```
[TypeScript playground example](https://www.typescriptlang.org/play/?target=99#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQFA24A2a6AsgJ4CiDcWAHjFgB2AE3QAJACosAMlywgh8AN404auAG0ADAF0AhAC44ARgBMAZjgB6K3FQksPflGSCsEAK7oA1ljZwAd0dhCEEyeADXeGBBOBgAC0cAKQBlAA06dTgCCAgAIWQoQxMLa1t7BTgIBKwoODBiMFqYfyDAqLgYuMS4VLS4AApBCDsm3GBkbhhKZBgFQRgASkz1GzgJHr64tia7eM8GYTh45AA3Rxq7LBgBOpbdiAJu4HQGiCbYNlV1AHNr7NyA0WRkEHhAACNanAlJQYB4oLEtABfb5qVD-HIQAanIwJF4aMgAfXRMEJmLIOkW0JRqLgxOuZNyxTI5LgAB9yODCmR2XBQRDais1GsNsl0ttdqh9h5DsczhceiTbhLHI9nq9Gs0vlk-vAuVAgSCwZC6jDrvDESisiS4Prsbj4viiSTGRAKVSlDSsvTSfriuDcgwcLEOfyTTQaSLHegTuhLvcsOg2pEFgC6m8wOguls+A1E6hgKFUDQE3B2Fw+gAFRroAC8vXS1feqAAPOWGAAaciYgpQHkclm5fuc7kAPjowiwjEKjhAEGEMscFCoMB5KiygmQClQYDwYv666y6hitwIe7gAEkFlAYgXcHJ5jB0Iej0eyCA2ABaLAMMhGdtVjWtJZFa6gojSuBFvAABWqC8BIibwPWLYft+DC0pitZKIObpImUVTeLS+pYdMHhYHhawQIRtJrEIqDwo4MSJDeAhHFsGbNMAiZOC8MC0sAwhYQAROCTDxEJoFoi0QZYUokEMNARjLsIZBIiio4tlYqE-uO9BQXAsHwYhphwMh2noVkmFKGY5gUbYVFEYUWE2XZBE0BpWlfjpdCQYIqAwXBCH+ZYZleRZqy2AAAk+368GMMDflAxB1AGEBBq4nToMM8DIHApyTGReUFVgGG5CRUBkZJ+HRagsXxYlyV2NMMQ-JlfLVCgRUMIV+XdSVWTEUoQk2RJ7maeZulrKAYBBo+syFrENhLctK2rWt60bWtdCloBzYthIXaVsAuC+MIADSfjoHwAgiD4fhqtgc7nNWWAEMAvCJi2j0QOcACCuC4Pm0CthIo5dik1y3BIOxYC9b28KOo6mXAtIAPIgMAMBfSuAB0AAi1zIMAQbCJIMhNpm2N4DAONk9Iv03De4IeAIINg+s7NHSdWDnZd45ZAAZHAlaFDAEwMC2XPeNjT2w5Q8Ofd9f0A0DUBs+DkO1NDTRw+9nPHadF1sKgiPbTDcApM1gg-EbqCo4IDBsPtSP1gAqoIC0AOqY9KWMHXyxpQhyqBsBCaW6aW7tez7nh+122tYBIEBK1gLvrE4N2iOsMNJyncAAPx8lg5x1EYEgANxm7sKe6x9IOHfL70Z0IWf+Te1tp4eGhnZ0sS+GwaoSCg6A99dLfoG3LUF5g8g-XLr3vS2Z0NwvCNwEYZ06GX3c6JXNKljXje8Ptze3U17c-Cv8On63Vs-GnQ9j2fAAGAAkSi10i78xAQULYP5SJn7T3-vAMulcSzmxTv9QGqB7Bq2dsjF8mge5dH7oPYecBR78HHjPWWtcWyWwvrbe2jsCGaygAnW2AAxaAzt2YQxuFrGGtckaFzcCXdemCt7rB3hGKujgGFQxhtQ2hoNkZoKeLXKW+0NaMIocwo+EdzZSINjI4WR8b4Tzvp3b43de5wAkenNAmDNFwDfh-I+X8lCT2toA6ePcQTF1qNwiQO897gNLIIphOsNH1mdAyWgQA) (same example from #60162)

The workaround in point 3 is cumbersome, but that is the only way.

The idea in https://github.com/microsoft/TypeScript/issues/60162 would help make the mapped type workaround simpler, but would not solve the actual JSX issue.

贡献指南

打开贡献指南

调研方向

未指定任何仓库文件或测试。首先比较链接的 playground 示例中的 JSX 类型检查行为与相关 issue #60162,然后追踪 TypeScript 编译器对 JSX props 以及 getter/setter 类型的处理。完成标准是 JSX 接受属性的 setter 所允许的值,同时不改变生成的 JavaScript。

由索引模型根据 Issue 内容生成。

评估

技术栈
typescript
领域
compilers
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
停滞
描述清晰度
基本清楚
新手友好度
30/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。