70

While reading through the TypeScript types for React, I saw a few usages of this pattern involving a bivarianceHack() function declaration:

@types/react/index.d.ts

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

Searching didn't lead me to any documentation on why this particular pattern was used, although I've found other instances of this pattern in use so it seems it's not a React-specific pattern.

Why is this pattern being used rather than (event: E) => void?

zzzzBov
  • 174,988
  • 54
  • 320
  • 367

2 Answers2

84

This has to do with function compatibility under the strictfunctionTypes option. Under this option if the argument is of a derived type you can't pass it to a function that will pass in a base class argument. For example:

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) => { } // fails under strictFunctionTypes

There is however a caveat to strict function type, stated in the PR

The stricter checking applies to all function types, except those originating in method or constructor declarations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array<T>) continue to mostly relate covariantly. The impact of strictly checking methods would be a much bigger breaking change as a large number of generic types would become invariant (even so, we may continue to explore this stricter mode).

Emphasis added

So the role of the hack is to allow the bivariant behavior of EventHandler even under strictFunctionTypes. Since the signature of the event handler will have its source in a method declaration it will not be subject to the stricter function checks.

type BivariantEventHandler<E extends Animal> = { bivarianceHack(event: E): void }["bivarianceHack"];
let o2: BivariantEventHandler<Animal> = (o: Dog) => { } // still ok  under strictFunctionTypes 

Playground link

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 8
    Cool, so first they enable controversial flags, then they use hacks to avoid following rules dictated by that flags. – fires3as0n May 17 '22 at 00:13
0

As previously mentioned, this is to work around the --strictFunctionTypes which affects the assignability of a function's type on its parameters.

I thought it was worth demonstrating this further.

Example

Playground

Explanation

'foo' | 'bar' is a subtype of string, or in TypeScript you could say 'foo' | 'bar' extends string.

When used as parameters to function F, the assignability is inverted.

F<string> extends F<'foo' | 'bar'> is correct, which shows contravariance on the type parameter.

In the above example, there are two tests each for the function on its own and then with the bivariance hack applied. The first tests for contravariance and the second for covariance.

Without the hack applied, the function is contravariant only. Whereas after the hack, it's contravariant and covariant, so we can say it's bivariant.

There's a really good article about this concept specifically in TypeScript, articulated much better than I'm able to here.

james2mid
  • 35
  • 2
  • 5