This series is updated as a patch for the TypeScript Getting Started Practical Notes course (see Checkbox Education).
The constraint relations
This article grew out of a discussion with colleagues about generic type inference, as shown in the following example (playground) :
type Func = (p: any) = > void
function foo<T extends Func> (p: T) {}
function bar<T> (p: T) {}
foo((p = 1) = > {}) // F1
foo((p: number = 1) = > {}) // F2
bar((p = 1) = > {}) // B1
bar((p: number = 1) = > {}) // B2
Copy the code
As mentioned in lecture 10 on generics, when a generic input parameter defaults (i.e., when a generic type is missing and no default type is specified), type inference is performed based on the argument. So in the example above, the input parameters of the foo and bar functions are called for type inference.
Among them, the input parameter types inferred in F2, B1 and B2 are quite understandable, i.e. (P? : number) => void. Function type foo, however, has an extended constraint (extends Func) over bar. It constrains the type of an input parameter to function foo but does not constrain the type of an input parameter (function) (specifying that the type is any instead), so the type inferred by the F1 annotation is (p? : any) => void.
We need to impose constraints on foo’s input type, such as adding a generic input parameter to Func:
type Func<P = any> = (p: P) = > void
function foo<T extends Func> (p: T) {}
Copy the code
But this doesn’t actually constrain the type of argument, so we need to modify the playground further:
type Func<P = any> = (p: P) = > void
function foo<T> (p: Func<T>) {}
function bar<T> (p: T) {}
foo((p = 1) = > {})
Copy the code
This seems to work, but the actual inferred type is Func
, equivalent to (p: unknown) => void. Interesting! (p = 1) => {} As a function as a whole, the type of parameter p is affected by the default value. However, when constrained by generic input parameters, it seems to be treated as an individual, directly taking the explicit type unknown. By default, the input parameter of a generic type is unknown. Therefore, foo definitely cannot infer the type of the input argument (p = 1) => {} to (p? : number) = > void or (p: number | undefined) = > void.
Also big in principle, the generic into and be bound by it into the refs, when doing type inference, who is more clear (specified), into the type of participation is affected by the who (but if explicitly specify generic parameter, so need not type inference), such as the following example (plaground), affected by the generic into the default type refs, when a reference type is not clear, F1, B1, we deduce an any.
type Func<P = any> = (p: P) = > void
function foo<T = any> (p: Func<T>) {}
function bar<T = any> (p: T = (() => void 0) as any as T) {}
foo((p = 1) = > {}) // F1
foo((p: number = 1) = > {})
bar() // B1
bar((p: number = 1) = > {})
Copy the code
The magic of invalid constraints
See example playground:
type Func = <P>(p: P) = > void
function foo<T extends Func> (p: T) {}
function bar<T> (p: T) {}
foo((p = 1) = > {}) // ts(2322)
foo((p: number = 1) = > {}) // ts(2345)
Copy the code
Because Func corresponds to the input parameter P of the generic type and does not really constrain the type of the parameter T of the function foo’s generic input parameter (function type), both calls to foo will give a type error because P can be of any type. It’s interesting, such as can be used to implement isEqual ([playground] www.typescriptlang.org/play?#code/…
type isEqual<A, B> = (<P>() = > P extends A ? 1 : 2) extends (<P>() = > P extends B ? 1 : 2)?true : false;
type E1 = isEqual<never.any>;
type E2 = isEqual<any.any>;
Copy the code
As the example above shows, the extends holds (more issue) if and only if the input arguments of A and B are exactly the same.