microsoft / microsoft/TypeScript

Keyword for inferring a new type from destructured parameters

Ouverte
#55,908 9 commentaires 8 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Awaiting More Feedback Suggestion
Langage dominant
Go
Étoiles
111k
Forks
14.4k
Merge moyen
1 j 19 h
PR mergées (30 j)
117

Description

🔍 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 👆

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par lire les issues liées 42419, 48220 et 7576, puis comparez leurs discussions avec la syntaxe from proposée et le comportement existant des paramètres déstructurés. Déterminez si seuls les paramètres de fonction sont concernés, comment le type Pick inféré doit se comporter et quels tests de syntaxe et de vérification de types permettraient d'établir que le travail est terminé.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Fonctionnalité
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Calme
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.