Why does TypeScript opt to utilize "extends keyof" when declaring generic type parameters with constraints, instead of using "in keyof"?

typescriptheaven.com, in its article discussing Generics, elaborates on 2 scenarios where the keyword "extends" is utilized when defining type parameters.

  1. In Generic Constraints.
interface Lengthwise {
  length: number;
}
 
function processingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length);
  return arg;
}
  1. When specifying a type parameter restricted by another type parameter.
function getValue<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
 
let y = { p: 1, q: 2, r: 3, s: 4 };
 
getValue(y, "p");

getValue(y, "z");
// Error: Argument of type '"z"' is not assignable to parameter of type '"p" | "q" | "r" | "s"'.

The use of the "extends" keyword while constraining a generic type makes sense as it ensures that the type should possess the properties and methods of the extended type (see the first instance above). This aligns with the usage of "extends" keyword in OOP inheritance.

However, what prompts the utilization of the "extends" keyword in the second case mentioned above to restrict the generic type parameter "Key" to be a property of the type parameter "Type"? Why not opt for "in" like "in keyof Type," which seems more fitting?

We appreciate your input and feedback!

Note: I acknowledge that "in keyof" is employed when defining mapped types as depicted below.

type Optional<T> = {
    [K in keyof T]?: T[K];
};

My query revolves around why not employ the same approach when limiting a generic type parameter to be a property of another type parameter?

Answer №1

It is really a matter of extends as opposed to in, assuming that when you say extends you are referring to "any subtype of" and by in you mean "a single member of". (Different interpretations of these terms can turn this into a subjective question.)

In the context of a mapped type, using P in K implies that P represents a single union member within K.

However, this distinction is not necessary for a generic constraint. With P extends K, P can encompass any subtype of K, allowing for more than just a single union member. For example:

getProperty(x, Math.random() < 0.5 ? "a" : "d");
/* function getProperty<{
    a: number;
    b: number;
    c: number;
    d: number;
}, "a" | "d"> */

In this scenario, Key is inferred as the union "a" | "d". The type "a" | "d" does not fit the definition of "in"

"a" | "b" | "c" | "d"
; it's merely a subtype. Key can be any subtype of
"a" | "b" | "c" | "d"
, such as the complete union, "a" | "d", or even never or complex combinations like "b" & {oops: true}. While usually getKey() will infer Key as an individual key, manual specification of another type is possible.

Having TypeScript introduce a concept of "this type parameter must be a single member of this union" would be valuable, with ongoing discussion in feature requests at microsoft/TypeScript#27808 and microsoft/TypeScript#33014:

interface Foo {
    a: string,
    b: number
}
function foo<<K extends "a" | "b">(k: K): Foo[K] {
    if (k === "a")
        return "abc"; // error! k is "a" but K might be "a" | "b"
    else
        return 123; // error! k is "b" but K might be "a" | "b"
}

The request suggests using extends oneof or extends_oneof. If implemented, perhaps in could be utilized instead, although syntax isn't the focal point. Presently, there exists only one form of generic constraint in TypeScript, signifying "any subtype", hence why extends is favored over in.

Playground link to code

Answer №2

When dealing with types, the relationship between "a" and

"a" | "b" | "c" | "d"
is important as it signifies subtype relationships.

The use of the extends keyword in programming languages denotes class inheritance, highlighting that an inheriting class is a type of the inherited class.

An interesting operator to consider in type transformations is keyof, which can be written as Key extends TypeKeys for clarity.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a specific instance where it would be more appropriate to utilize the styled API for styling as opposed to the sx prop in Material-

I am currently in the process of migrating an existing codebase to Material UI and am working towards establishing a styling standard for our components moving forward. In a previous project, all components were styled using the sx prop without encounteri ...

Ionic 2: Unveiling the Flipclock Component

Can anyone provide guidance on integrating the Flipclock 24-hours feature into my Ionic 2 application? I'm unsure about the compatibility of the JavaScript library with Ionic 2 in typescript. I have searched for information on using Flipclock in Ionic ...

What is the best way to implement record updates in a nodejs CRUD application using prisma, express, and typescript?

Seeking to establish a basic API in node js using express and prisma. The schema includes the following model for clients: model Clients { id String @id @default(uuid()) email String @unique name String address String t ...

Step-by-step guide to minify Typescript files with Webpack

I am facing an issue in my webpack configuration while trying to minify the output bundle when working with TypeScript. Currently, only one file has been migrated to TypeScript in my project and it works fine with babel-node and the dev bundle without Ugli ...

How can a border be applied to a table created with React components?

I have been utilizing the following react component from https://www.npmjs.com/package/react-sticky-table Is there a method to incorporate a border around this component? const Row = require("react-sticky-table").Row; <Row style={{ border: 3, borderco ...

What is the best way to declare this massive entity in typescript?

In the process of parsing a file, a large object is returned by the main function. function parse(file){ /* dostuff.. */ return myObject } The order of determining properties is crucial (e.g., "a" must be determined before "b" or the value will be differe ...

Accessing the Component Property from an Attribute Directive in Angular 2

Currently, I am in the process of creating filter components for a grid (Ag-Grid) and planning to use them in various locations. To make these filters accessible from different places, I am developing a wrapper for them. In particular, I am working on a fi ...

What is the method to determine the length of a string with TypeScript?

Looking to derive the numerical length of a string: type Length = LengthOfString<"hello"> // ^? should equal 5 Feeling a bit lost on how to approach this. Any guidance on how to achieve this? (Currently diving into typescript's typ ...

Adjust the appearance of matSelect when the selection menu is activated

What is the best way to adjust mat-select properties when its options are open? <mat-select class="selector"> <mat-option><mat-option> </mat-select> .selector:focus { color: green; } I attempted using focus, but ...

Issue with dynamically typed object properties in Typescript codebases

Check out this TypeScript code snippet: export type Mutation = (state: State, data: unknown) => void export type Mutations = { [key: string]: Mutation } private buildMutations(): Mutations { return { ['3']: (state, data) => ...

Can you explain the variance between the (Record<string, unknown>) and object type?

Can you explain the distinction between type Record<string, unkown> and type object? Create a generic DeepReadonly<T> which ensures that every parameter of an object - and its nested objects recursively - is readonly. Here's the type I c ...

The value is not being found in my form, and the slide-toggle is consistently checked

I am encountering an issue with my forms. On the web, all my slide-toggle options are pre-checked as shown in the image provided. I suspect that the problem lies within the patchFor(){} function. Could someone please review my code for me? I have attempte ...

Requires the refreshing of an Angular component without altering any @Input properties

Currently delving into the world of Angular (along with Typescript). I've put together a small application consisting of two components. This app is designed to help track work hours (yes, I am aware there are commercial products available for this pu ...

"Encountering an error with the any type in the useLocation feature while using React Router version 6

https://i.sstatic.net/0YcS9.png What steps should I take to resolve this particular type of error issue? My attempt at passing a custom type did not yield successful results. ...

Ways to turn off Typescript alerts for return statements

I'm looking to turn off this Typescript warning, as I'm developing scripts that might include return values outside of a function body: https://i.stack.imgur.com/beEyl.png For a better example, check out my github gist The compiled script will ...

Apache ECharts is throwing an error due to incompatible types of the 'trigger' property

I am experimenting with setting up some options in this demonstration, and here is what I have managed to achieve so far. testOptions: EChartsOption = Object.assign( {}, { backgroundColor: 'red', tooltip: { trigger: ...

Is it possible to utilize an npm package in TypeScript without a d.ts definition file?

Is it possible to use an npm package in TypeScript and Node.js without a .d.ts definition file? If so, how can I make it work? Currently, my code looks like this and I'm getting an error that says "cannot find module 'node-rest-client'" bec ...

Frequent occurrence when a variable is utilized prior to being assigned

I am currently working with a module import pino, { Logger } from 'pino'; let logger: Logger; if (process.env.NODE_ENV === 'production') { const dest = pino.extreme(); logger = pino(dest); } if (process.env.NODE_ENV === &apo ...

Error encountered: TypeError: Unable to access attributes of null object (attempting to read 'useMemo')

In the development of a public component titled rc-component version0.1.14, I built a platform that allows for the sharing of common React pages amongst various projects. However, upon attempting to utilize this component in my project, I encountered the f ...

Unit Testing in Vue.JS: The original function remains active even after using sinon.stub()

While unit testing my components (which are coded using TypeScript along with vue-class-component), I am utilizing Sinon to stub API calls. However, even after adding the stub to the test, the original method is still being invoked instead of returning the ...