TypeScript$Type-Manipulation-Generics
0.0 What is Generics
Generics 泛型是什么?泛型就是某个类型——我知道需要一个类型,但是我不知道具体是什么类型,所以我暂时用一个 T(或者任何其他标识)来表示这个类型。当你使用的时候,自己声明是什么。(具体实现而言,我们需要使用 <T> 来表示泛型 T,在需要类型的地方使用 T。这个 T 叫做 type variable。)🤨
- Designer:泛型在表示类型的时候,给某个或某些类型占了个位(通过 type variable / type parameter
<T, V>)。 - User:当具体使用的时候,占位的类型就确定了(type argument)。
之所以使用泛型,是为了保留未知数据的类型。比如一个函数接受一些参数,这些参数是有类型的;函数的返回值是根据这些参数决定的。参数和返回值的类型的关系如何保持呢?需要一个中间类型来暂存,这个中间类型,就是上面说的 type variable(占位用)。
function identity<Type>(arg: Type): Type {
return arg;
}
上面的代码可以这样理解:函数 identity 为了关联入参 arg 和 返回值的类型的关系,需要一个 type variable 来暂存。实现方式是:在 identity 后面声明 type variable:<Type>,然后就可以在入参和返回值的类型那里使用 Type 来表示这个类型。
let output = identity<string>("myString");
let output = identity("myString"); // type argument inference
0.1 Why Generics
使用 <T> 和 any 的区别是什么呢?从 Designer 编写泛型的地方(比如 function)来看,似乎没有什么用。但是从 User 使用泛型的地方来看,泛型保存了类型。
比如上面的 identity 函数。如果使用 any,那么 output 的类型就是 any;当使用 <T> 后,output 的类型就变成了 string。(如果传入的是其他类型,那返回的也是那个类型。)
Generic 泛型不生产类型,它只是类型的搬运工。
1. Generic Types
Generic types 泛型类型是什么呢?就是使用了泛型的类型。 什么类型可以加泛型参数呢?自然是复杂类型,因为复杂类型有结构。
- function:函数有入参和返回值,这些值的类型可以通过泛型确认(在使用时)
- interface(object):
- interface 本身使用泛型:内部可以使用该泛型。需要在使用的时候确定。
- interface 内部使用泛型:比如内部有 function,就像普通的 function 一样。(这其实应该算是 function 的泛型)
- class:类上的泛型是给实例用的。
- array:
T[]/Array<T>
Generic Functions
Generic functions 的类型只比普通 function 的类型前多了 type variable(s) <T> (<T, U>)。
如果在 function 上直接标注类型,则需要把泛型参数加在函数名后。
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: <Type>(arg: Type) => Type = identity; // type of generic function
// a different name
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: <Input>(arg: Input) => Input = identity;
// call signature
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: { <Type>(arg: Type): Type } = identity;
Generic Interfaces
// generic interface - generic in interface
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
// generic interface 2 - generic with interface
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity; // number
Generic Classes
和 generic interfaces 类似。类型是给实例用的。
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
2. Using Class Types in Generics
工厂函数需要声明 class types。
function create<Type>(c: { new (): Type }): Type {
return new c();
}
A more advanced example uses the prototype property to infer and constrain relationships between the constructor function and the instance side of class types.
This pattern is used to power the mixins design pattern.
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = "Mikle";
}
class Animal {
numLegs: number = 4;
}
class Bee extends Animal {
numLegs = 6;
keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
3. Generic Parameter Defaults
Generic parameters 可以有默认值,这有时会简化声明。
declare function create(): Container<HTMLDivElement, HTMLDivElement[]>;
declare function create<T extends HTMLElement>(element: T): Container<T, T[]>;
declare function create<T extends HTMLElement, U extends HTMLElement>(
element: T,
children: U[]
): Container<T, U[]>;
declare function create<T extends HTMLElement = HTMLDivElement, U extends HTMLElement[] = T[]>(
element?: T,
children?: U
): Container<T, U>;
const div = create();
// const div: Container<HTMLDivElement, HTMLDivElement[]>
const p = create(new HTMLParagraphElement());
// const p: Container<HTMLParagraphElement, HTMLParagraphElement[]>
- A type parameter is deemed optional if it has a default.
- Required type parameters must not follow optional type parameters.
- Default types for a type parameter must satisfy the constraint for the type parameter, if it exists.
- When specifying type arguments, you are only required to specify type arguments for the required type parameters. Unspecified type parameters will resolve to their default types.
- If a default type is specified and inference cannot choose a candidate, the default type is inferred.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a default for an existing type parameter.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a new type parameter as long as it specifies a default.
4. Working with Generic Type Variables
<T> 意味着类型 T 可以是任意类型,所以只有所有类型都通用的操作才合法。
有时候我们对 T 有要求,这时可以限制 T 的类型。
Generic Constraints <T extends U>
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
Using Type Parameters in Generic Constraints
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m");
// error, Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.