# 泛型

## 1. 泛型函数

定义一个 identity 函数

```
function identity<T>(arg: T): T {
    return arg;
}
```

给 identity 函数添加了 类型变量`T`. T 会捕获用户传入的类型，(比如: number)，之后 `T` 就代表这个类型了。

这个版本的 identity函数 叫做泛型函数,因为 T 可以是任意的类型。

泛型函数的两种调用方法:

```
1. 传入所有的参数，包括类型参数，其中类型参数用 <> 括起来
const output = identity<string>('123');
即明确指定 T 就是 string, 因此会限制 arg 必须为 string 类型。
因此下面的代码会报错:  
const output = identity<string>(123);  因此 123 不是 string 类型，所以会报错。


2. 可以不传类型参数，编译器自己查看 '123' 的类型，自动推算出 T 为 string
const output = identity('123');
```

## 2. 泛型变量

想象下以下代码，因为 return 的值必然是个字符串，而 T 不一定是 string, 所以会报错。

```
function identity<T>(arg: T): T {
    return arg+'123';
}
```

又或者：

```
function identity<T>(arg: T): T {
    console.log(arg.length);  // T 类型不一定有 length 属性(T 为 string 时有)，所以会报错
    return arg;
}
```

又因为数组具有 `.length` 属性，因此可以像下面这样：

```
function logIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);    // 数组中确定是有length属性的
    return arg;
}
```

即我们可以把泛型变量`T`作为类型的一部分，实际类型 `T[]` 由 `T` 构成，增加了灵活性。

## 3. 泛型类型

```
function identity<T>(arg: T): T {
    return arg;
}

const myIdentity: <T>(arg: T) => T = identity
这段代码其实就是 const myIdentity = identity; 
<T>(arg: T) => T 只是类型描述
```

`const myIdentity: <T>(arg: T) => T = identity`\
又会等同于\
`const myIdentity: { <T>(arg: T): T } = identity`

因此我们可以定义一个泛型接口:

```
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T{
    return arg;
}

const myIdentity: GenericIdentityFn = identity
```

接受参数的泛型接口:

```
// 接受一个类型参数，作为T
// 这样接口里的其他成员也能知道这个参数的类型
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T) :T {
    return arg;
}

const myIdentity: GenericIdentityFn<number> = identity;
```

## 4. 泛型类

与泛型接口类似

```
class GenericNumber<T> {
    zeroValue: T;
    add: (x:T, y:T) => T;
}

const myGenericNumber = new GenericNumber<number>();  // 必须传入类型 number
myGenericNumber.zeroValue = 1;
myGenericNumber.add = function(x,y) = { return x + y };
```

**类有静态部分和实例部分，泛型类只能用于实例部分的类型，类的静态属性不能使用这个泛型类型。**

## 5. 泛型约束

首先以下代码会报错:

```
function logIdentity<T>(arg: T):T {
    console.log(arg.length); // 不一定会有 .length 属性
    return arg;
}
```

因此我们需要约束类型 `T` 必须要有 `length` 属性, 我们可以定义一个接口来描述约束条件，使用接口和`extends`关键字来实现约束：

```
interface LengthWise {
    length: number;
}

function identity<T extends LengthWise>(arg: T) :T {
    console.log(arg.length);  // 不会报错，接口限制了类型 T 会有 length 属性
    return arg;
}
```

此时，要调用 `identity`， 那么参数必须有 `length` 属性

### 5.1 泛型约束中使用类型参数

声明一个类型参数，被另一个类型参数所约束。

```
function getProperty<T, K extends keyof T>(obj: T, key:K) {
    return obj[key];
}

const x = {a: 1, b :2};

getProperty(x,'a')
getProperty(x,'m')    // m 不是对象 x 的key，因此会报错
```

关于 `keyof`:

```
interface Person {
    name: string;
    age: number;
    location: string;
}

type k1 = keyof Person;    // "name" | "age" | "location"   对象的属性名
type k1 = keyof Person[];    // "length" | "push" | "pop"  数组的属性名
```

因此 `K extends keyof T`，则 `K` 必须是对象 `T` 的某个属性名

### 5.2 泛型中使用类类型


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yes-1-am.gitbook.io/blog/typescript-yu-fa/fan-xing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
