Allow different properties between the JSX expression and component type

オープン
#55,248 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
25/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
typescript
領域
compilers

調査の方向性

この issue には実装ファイルやテストが記載されていません。まず、提案されている JSX.convertProperties の設計、動機となった例、関連付けられた TypeScript の設計目標を確認し、次に JSX の型チェックのエントリポイントを特定してください。実装とテストの指針となるだけの十分な精度で、受け入れられる動作と互換性の境界が定義できれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Awaiting More Feedback Suggestion

Suggestion

🔍 Search Terms

  • jsx functional component properties
  • property type
  • parameter type
  • jsx property cast

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Concept based on #14729

Introduce a new function to the JSX namespace called convertProperties (subject to change) which takes a component and returns an object representing its JSX props.

declare namespace JSX {
    // today's behavior
    function convertProperties<P>(component: (JSX.ElementClass & { new(): { props: P } }) | ((props: P) => JSX.Element)): P;

    // alternatively, if this were to supercede IntrinsicAttributes rather than just working alongside it
    function convertProperties<P>(component: (props: P) => JSX.Element): P & JSX.IntrinsicAttributes;
    function convertProperties<P, T extends { props: P }>(component: JSX.ElementClass & { new(): T }): P & JSX.IntrinsicAttributes & JSX.IntrinsicClassAttributes<T>;
}

When the typechecker sees a component expression (e.g. <MyComponent prop={val} />), it will determine the types of the required properties by performing a virtual call to JSX.convertProperties with the provided component type and using the return type. For example:

type SignalProps<P> = { [K in keyof P]: () => P[K] };

declare namespace JSX {
    function convertProperties<P>(component: ((props: SignalProps<P>) => JSX.Element)): P;
}

type MyProps = SignalProps<{
    title: string;
    count: number;
}>;

function MyComponent({ title, count }: MyProps) {
    return <div>{title()}: {count()}</div>;
}

function App() {
    // typeof MyComponent == (props: { title: () => string, count: () => number }) => JSX.Element
    // typeof JSX.convertProperties(MyComponent) == { title: string, count: number }
    return <MyComponent title="Hello, World!" count={3} />;
}

📃 Motivating Example

The Solid framework is very similar to React, but rather than tracking reactive dependencies with an explicit dependencies array like in React's useMemo and useEffect, Solid requires calling a function to retrieve the value of a piece of state, allowing for implicit dependency tracking based on which states were previously fetched the last time the memo/effect callback ran (see https://www.solidjs.com/guides/reactivity#introducing-primitives). It is a similar concept to Knockout's observables.

However, when it comes to component properties, Solid uses proxy dereferences instead of explicit function calls. This leads to a common footgun where destructuring the props object like one might do in React will prevent dependencies from being tracked (https://www.solidjs.com/guides/faq#why-do-i-lose-reactivity-when-i-destructure-props). An alternative design could have had the jsx factory function wrap every prop in a reactive getter before bundling them into the props object.

By separating the prop types between the component creation and the JSX where it's used, TypeScript would be able to support such a design in a new web framework.

💻 Use Cases

See above.

Another similar use case could be:

type DetailedProps<P> = {
    [K in keyof P]: {
        value: P[K];
        prevValue?: P[K];
    }
}

declare namespace JSX {
    function convertProperties<P>(component: ((props: DetailedProps<P>) => JSX.Element)): P;
}

This feature would also allow performing the reverse operation of JSX.IntrinsicAttributes. For example:

declare namespace JSX {
    function convertProperties<P>(component: ((props: P) => JSX.Element)): Omit<P, 'parent'>;
}

type PropsWithParent<T> = T & {
    parent?: JSX.Element;
};

function MyComponent({ parent }: PropsWithParent<{}>) {
    // ...
}

function App() {
    return <MyComponent />;
}

Alternative Designs

Alternatively, a solution which is slightly less powerful but likely just as useful in 90% of cases could be to declare generic types on the JSX namespace like is done with JSX.IntrinsicClassAttributes<T>:

declare namespace JSX {
    // same behavior as today
    type TransformProps<P> = P;

    // some use cases
    type TransformProps<P> = { [K in keyof P]: P[K] extends () => infer R ? R : never };
    type TransformProps<P> = Omit<P, 'parent'>;

    // or in reverse, deriving JSX prop types via inference like in the earlier examples
    type TransformProps<P> = SignalProps<P>;
    type TransformProps<P> = P & { parent?: JSX.Element };
}
主要言語
Go
スター
111k
フォーク
14.4k
平均マージ
1日 19時間
マージ済み PR(30日)
117

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

microsoft/TypeScript のほかの issue

microsoft/TypeScript の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。