microsoft / microsoft/TypeScript
Keyword for inferring a new type from destructured parameters
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Go
- Sterne
- 111k
- Forks
- 14.3k
- Ø Merge
- 1 T. 19 Std.
- Gemergte PRs (30 T.)
- 117
Beschreibung
🔍 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
- 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.
- 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.
- What workarounds are you using in the meantime?
The above 👆
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Lies zunächst die verlinkten Issues 42419, 48220 und 7576 und vergleiche anschließend deren Diskussionen mit der vorgeschlagenen from-Syntax und dem bestehenden Verhalten bei destrukturierten Parametern. Kläre, ob nur Funktionsparameter im Umfang liegen, wie sich der inferierte Pick-Typ verhalten soll und welche Syntax- und Type-Checking-Tests den Abschluss belegen würden.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- typescript
- Bereich
- compilers
- Issue-Typ
- Feature
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Aktivitätsstatus
- Ruhig
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 35/100