What exactly does bivarianceHack aim to achieve within TypeScript type systems?

As I went through the TypeScript types for React, I noticed a unique pattern involving a function declaration called bivarianceHack():

@types/react/index.d.ts

type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];

I searched but couldn't find any documentation explaining why this specific pattern was chosen. However, I did come across other instances of using this pattern, indicating it's not exclusive to React.

What is the reason behind using this pattern instead of (event: E) => void?

Answer №1

This issue revolves around the concept of function compatibility when using the strictfunctionTypes option in TypeScript. Essentially, under this setting, passing an argument of a derived type to a function that expects a base class argument is not allowed. An example to illustrate this:

class Animal { private x: undefined }
class Dog extends Animal { private d: undefined }

type EventHandler<E extends Animal> = (event: E) => void

let o: EventHandler<Animal> = (o: Dog) => { } // triggers an error under strictFunctionTypes

However, there is a specific exception mentioned regarding method or constructor declarations in the PR:

The stricter checking applies to all function types, except those originating in method or constructor declarations. This exclusion ensures that generic classes and interfaces like Array<T> still maintain covariance relationships. The decision not to strictly check methods was made to prevent a significant breaking change where many generic types would become invariant (although further exploration into a stricter mode may be considered).

Important emphasis on the above statement.

Therefore, a workaround has been introduced to preserve the bivariant behavior of the EventHandler even with strictFunctionTypes enabled. Since the event handler's signature originates from a method declaration, it remains exempt from the stricter function checks.

type BivariantEventHandler<E extends Animal> = { bivarianceHack(event: E): void }["bivarianceHack"];
let o2: BivariantEventHandler<Animal> = (o: Dog) => { } // continues to work with strictFunctionTypes 

You can also test this scenario in the TypeScript Playground here.

Answer №2

In order to address the --strictFunctionTypes issue that impacts how a function's type is assigned to its parameters, it was mentioned earlier.

I felt it was important to provide further illustration of this point.

Illustrative Example

Interactive Playground

https://i.sstatic.net/2DFBB.png

Clarification

'foo' | 'bar' is considered a subtype of string, or in TypeScript terms, you could express it as 'foo' | 'bar' extends string.

When utilized as parameters for function F, the assignability behaves differently.

The equation

F<string> extends F<'foo' | 'bar'>
showcases contravariance within the type parameter.

In the example above, there are two tests conducted for the function independently and then with the bivariance workaround implemented. The first test explores contravariance, while the second examines covariance.

In absence of the workaround, the function displays contravariant behavior exclusively. However, after applying the hack, it exhibits both contravariant and covariant characteristics, making it bivariant.

An informative article on this specific concept in TypeScript can be found here.

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

Having trouble getting Tailwind CSS utility classes to work with TypeScript in create-react-app

I have been struggling to troubleshoot this issue. I developed a React application with TypeScript and integrated Tailwind CSS following the React setup guidelines provided on the official Tailwind website here. Although my code and configuration run succ ...

Encountering a compiler error due to lack of patience for a promise?

In the current TypeScript environment, I am able to write code like this: async function getSomething():Promise<Something> { // ... } And later in my code: const myObject = getSomething(); However, when I attempt to use myObject at a later po ...

Issue TS2365: The operation of addition cannot be performed between an array of numbers and the value -1000

I'm encountering an error in my ng serve logs despite the function functioning properly with no issues. However, I am concerned as this may pose a problem when building for production. How can I resolve this error? uuidv4() { return ([1e7]+-1e3+- ...

Retrieving information from a .json file using TypeScript

I am facing an issue with my Angular application. I have successfully loaded a .json file into the application, but getting stuck on accessing the data within the file. I previously asked about this problem but realized that I need help in specifically und ...

Drizzle-ORM provides the count of items in a findMany query result

Hello there, I'm currently experimenting with the Drizzle ORM and imagine I have this specific query const members = await trx.query.memberTable.findMany({ with: { comments:true } }) I'm wondering how I can retrieve the total count of me ...

Angular is not programmed to automatically reflect updates made to my string array

let signalRServerEndPoint = 'https://localhost:44338'; this.connection = $.hubConnection(signalRServerEndPoint); this.proxy = this.connection.createHubProxy('MessagesHub'); this.proxy.on("ReceiveMessage", (message) => { ...

Change a TypeScript alias within the @types namespace

While using Typescript 3, I encountered a situation where I needed to modify a type alias from the @types/json-schema definition provided by DefinitelyTyped. The issue arose when I wanted to incorporate a custom schema type into my code. Since this custom ...

Ensure that Angular resolver holds off until all images are loaded

Is there a way to make the resolver wait for images from the API before displaying the page in Angular? Currently, it displays the page first and then attempts to retrieve the post images. @Injectable() export class DataResolverService implements Resolv ...

The names of properties in Typescript are determined by the values of the outer type properties

In my project, I have various interfaces (or types) defined as follows: export type simpleValue = string | number | boolean | Date | null; export interface Options { inline?: OptionsItem[] | unknown[]; promptField?: string; selectedValues?: unknown[ ...

Using ternary operator to set multiple variables in setState

Conditional Operator for Setting State in React I am wondering if there is a way to set the state with a variable that holds the correct state value using setState method. interface state { isfiltered: array<boolean> } this.setState({ ...

Directly retrieve the result from http service (observable) without the need to return Observable from the function

Is there a way to directly return a result from the service without returning Observable and then using then clause? I've experimented with methods like pipe, of, take, toPromise, map, async-await, but none of them seem to return the result on a servi ...

The interface does not allow properties to be assigned as string indexes

Below are the interfaces I am currently working with: export interface Meta { counter: number; limit: number; offset: number; total: number; } export interface Api<T> { [key: string]: T[]; meta: Meta; // encountered an error here } I h ...

Setting up NextJs in Visual Studio Code with Yarn

When I used yarn create next-app --typescript to set up a TypeScript Next.js application with Yarn, everything seemed to be working fine with the command yarn run dev. However, Visual Studio Code was not recognizing any of the yarn packages that were added ...

The production build encountered an error after upgrading Angular due to a problem with document.documentElement.setAttribute not being recognized

Recently, I updated my application to Angular 16 and the upgrade was successful. The application is running smoothly without any issues. However, when I attempted to run the command for a production build, ng build --configuration=production I encountere ...

Guidelines for utilizing NgFor with Observable and Async Pipe to create Child Component once the data has been loaded

Encountering an issue while attempting to display a child component using data from an Observable in its parent, and then utilizing the async pipe to transform the data into a list of objects for rendering with *NgFor. Here's what I've done: C ...

Jest snapshot tests are not passing due to consistent output caused by ANSI escape codes

After creating custom jest matchers, I decided to test them using snapshot testing. Surprisingly, the tests passed on my local Windows environment but failed in the CI on Linux. Strangely enough, the output for the failing tests was identical. Determined ...

Oops! The system encountered a problem: the property 'modalStack' is not recognized on the type 'NgxSmartModalService'. Maybe you meant to use '_modalStack' instead?

Currently, I'm facing an issue while attempting to run ng build --prod in my Angular 6 project. I have also incorporated the NgxSmartModal package for handling modals. Unfortunately, the build process is failing and I can't seem to figure out why ...

Steps for combining a sequence of subsequent subscriptions in RxJS

In my approach, I followed the code below. this.service1.info .subscribe(a => this.service2.getDetails(a.id) .subscribe(b => { this.doStuff(b); }) ); Lately, it has come to my attention that we will be adding more steps that gradu ...

Ways to extract information from a database using a parameter

I am currently working with angular cli version 8.1.0 and I have a requirement to pass parameters in the URL and retrieve data from a PHP MySQL database. On the PHP side, everything is functioning correctly and the URL looks like this: http://localhost/rep ...

Can you explain the significance of the additional pipeline in the type declaration in TypeScript?

Just recently, I defined a type as follows- interface SomeType { property: { a: number; b: string; } | undefined; } However, upon saving the type, vscode (or maybe prettier) changes it to- interface SomeType { property: | { a: nu ...