microsoft / microsoft/TypeScript

Keyword for inferring a new type from destructured parameters

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

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

Awaiting More Feedback Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

🔍 Search Terms
destructuring, destruct, infer destruct, infer destructuring, function destruct, function destructuring

Issue https://github.com/microsoft/TypeScript/issues/42419 (+ https://github.com/microsoft/TypeScript/issues/48220) discusses changing the current behavior of TypeScript to address the same underlying issue.

My proposal is different as it introduces a new explicit language feature and does not change any current behavior.

I also found https://github.com/microsoft/TypeScript/issues/7576. The essence here is that they want some sort of shorthand for destructured parameter types.

I think this is vaguely related to my proposal since it addresses a way to do that—it touches on a similar pain point.

✅ Viability Checklist
  • 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 our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion

Let's introduce a feature that allows for TypeScript to infer a new type from destructured function parameters.

To conceptualize this idea, I'm introducing a new keyword called from which can be used after : when destructuring a function parameter:

type Person = { first: string, last: string, age: number };

const isAdult = ({ age }: from Person) => age > 18;

This declares that typeof isAdult is (person: Pick<Person, "age">) => ... instead of (person: Person) => ... meaning that any object matching { age: number } can be passed in as a parameter.

You could also potentially do this when destructuring a local variable, though, I struggle to see its usefulness:

const { age, first }: from Person = person;

I'm not even sure whether this should be legal.

📃 Motivating Example

We often use destructuring in functions, because it helps us stay DRY:

type Person = { first: string, last: string, age: number };

const formatName = ({ first, last }: Person) => `${first} ${last}`;
const isAdult = ({ age }: Person) => age > 18;

I think this is good practice and a sensible thing to include in your company style guide.

But if we look critically at the above snippet, we can see that the signatures for both formatName and isAdult are lying about their implementation. They (quite excessively) declare that they need all properties from Person when they actually only need a few. And rightly so: I have explicitly declared that by writing : Person, but my intention here was actually to be able to pass any object that matches { age: number } into isAdult (for example).

To solve this, I have to jump through some hoops, either like this …

type Person = { first: string, last: string, age: number };

const isAdult = ({ age }: Pick<Person, "age">) => age > 18;
const formatName = ({ first, last }: Pick<Person, "first" | "last">) => `${first} ${last}`;

... or the inverse:

type Named = { first: string, last: string };
type Aged = { age: number; }
type Person = Named & Aged;

const formatName = ({ first, last }: Named) => `${first} ${last}`;
const isAdult = ({ age }: Aged) => age > 18;

However…

… the first solution is noisy and un-DRY since have to write the "pick part" twice: first for the destructuring itself, and then for the Pick type. Note: this effect is greatly exacerbated as the number of properties of a type increases, so this example is quite tame in that regard.

… the second solution (workaround?) feels too verbose for my use case (there's too much detail), I have no intent of re-use for the Aged or Named type. If the Person type comes from a third-party library (or another source that I don't control, e.g. Prisma), then this option is out of the question entirely as I can't "build up" my own type from smaller fragments (i.e. Person = Aged & Named).

This means that the first solution is often the only viable option—and it just isn't a great experience.

So—with the addition of the from keyword (or something similar), this headache is entirely relieved! It would make it much more convenient to declare the explicit requirements of a function to effortlessly make your code more expressive.

💻 Use Cases
  1. What do you want to use this for?

This would have great utility in React components where you just want to pick a few props out of e.g. ButtonProps from @mui/material/Button:

function FooterButton({ color, href, children }: from ButtonProps)

However, I see many use cases for this: Anywhere where you need more generically defined function type signatures without having to create a new explicit type or use Pick/Omit.

  1. What shortcomings exist with current approaches?

You need to manually use Pick/Omit or break types into smaller (often hard-to-name) types and compose them later on, or just live with the "excessive" type definitions.

  1. What workarounds are you using in the meantime?

The above 👆

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

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

はじめの一歩

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

調査の方向性

まず、リンクされている issue 42419、48220、7576 を読み、次にそれらの議論を提案された from 構文および既存の分割代入パラメーターの動作と比較します。対象が関数パラメーターのみかどうか、推論される Pick 型がどのように動作すべきか、そしてどのような構文テストと型チェックテストによって完了を確認できるかを明確にしてください。

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

評価

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

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

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