# Typescript高级操作

tags: TypeScript Created time: December 24, 2022 8:42 PM emoji: <https://www.typescriptlang.org/favicon-32x32.png?v=8944a05a8b601855de116c8a56d3b3ae>

## keyof

keyof操作符的作用是遍历一个对象/接口获取键值。

```jsx
type Point = { x: number; y: number };
type P = keyof Point;
// 相当于
// type P = 'x' | 'y'

const p1:P = 123 // 报错
const p2:P = '888'  // 报错
const p3:P = 'x' // 正确
```

如果使用索引类型

```jsx
type Point = { [name: string]: number };
type P = keyof Point;
// 相当于
// type P = number
```

## typeof

在js中typeof主要作用是判定变量类型，而ts扩展了typeof关键字的作用，使其可以进行“读取类型”操作。

```tsx
const a= 123
const b: typeof a = 123
```

这对于基本类型不是很有用，但与其他类型操作符结合使用，可以使用typeof方便地表示许多模式。例如，让我们从查看预定义的类型ReturnType开始。它接受函数类型并生成其返回类型：

```tsx
function fn():string{
    return '123'
}

type fnType = typeof fn;  // type fnType = () => string
let a: ReturnType<fnType>;
a = '123'  // string
```

为了避免混乱，ts限制typeof关键字后面只能跟标识符(变量名、函数名等)或对象属性。

```tsx
// 错误
function fn():string{
    return '123'
}
let a:typeof fn() = '123'

// 正确
let o = {
    a: '123'
}
let a: typeof o.a = '123'
```

## **索引访问类型(Indexed Access Types)**

ts可以通过索引访问类型。可以通过`type`、`interface`或`对象`来设置类型。

```tsx
// 对象
let o = {
    a: '123'
}
let a: typeof o['a'] = '123'

// 接口
interface O1 {
    a: string
}
type Oa = O1['a']

// 类型别名
type O2 = {
    a: string
}
type Ob = O2['a']
```

甚至你还可以使用联合类型。

```tsx
type A = {
    a: string,
    b:number
}
type B = A['a' | 'b'] // string | number
type C = A[keyof A] // string | number
```

## Mapped Types

有些时候你可能要重复定义某些类型，如果你不想编写重复的代码，可以使用映射类型。

```tsx
type A<Type> = {
    [Property in keyof Type]: boolean;
};

type = {
    a: string;
    b: number;
};
 
type O = A<FeatureFlags>; // {a:boolean;b:boolean}
```

这里使用到了泛型， `[Property in keyof Type]` 会迭代`FeatureFlags` 所有的key，你也可以让所有的key定义为原来的类型。

```tsx
type A<Type> = {
    [Property in keyof Type]: Type[Property];
};
```

注意：在映射类型中不能定义属性和方法。

```tsx
type A<Type> = {
    [Property in keyof Type]: Type[Property];
		a: string; // 错误
		fn:()=>void; // 错误
};
```

## 条件类型

很多时候我们需要根据输入值的类型来判断输出值的类型，这个时候就可以使用条件类型。

条件类型的写法有点类似于 JavaScript 中的条件表达式（`condition ? trueExpression : falseExpression` ）：

```tsx
SomeType extends OtherType ? TrueType : FalseType;
```

例如：

```tsx
type A = number;
type B = A extends number ? number : string
const b:B = 1
```

条件类型配合泛型时就很有用了，例如我们需要实现一个函数，如果传入的参数是字符串则返回`string`，如果参数是数字类型则返回`number`，如果使用泛型+条件类型就可以这样写：

```tsx
function fn<O extends string | number>(arg: O):O extends string ? 'string' : 'number'{
    if(typeof arg === 'string'){
        return 'string' as O extends string ? 'string' : 'number'
    }else {
        return 'number' as any;
    }
}
```

可以将条件类型提取出来：

```tsx
type FnReturnType<O extends string | number> = O extends string ? 'string' : 'number'

function fn<O extends string | number>(arg: O): FnReturnType<O> {
    if(typeof arg === 'string'){
        return 'string' as FnReturnType<O>;
    }else {
        return 'number' as FnReturnType<O>;
    }
}

fn<string>('1')
```

## infer

`infer`需要搭配条件类型来使用，`**infer`的作用是从正在比较的类型中推断类型，然后在 `true`  分支里引用该推断结果。\*\*

我们用下面这个例子来描述`infer`的作用，在下面这个例子中，我们实现`ArrayItemType`类型来获取数组元素的类型，如果`T`是一个数组则返回其元素的类型，否则返回`null`，我们可以这样写：

```tsx
type ArrayItemType<T> = T extends any[] ?  T[number] : null;

const arr = [1];
const a: ArrayItemType<typeof arr> = 0;
```

这里运用了索引访问类型，如果使用infer，代码还能更简洁。

```tsx
type ArrayItemType<T> = T extends Array<infer K> ?  K : null;

const arr = [1];
const a: ArrayItemType<typeof arr> = 0;
```

注意，infer推断的类型只能在条件类型的true分支中使用，不能在false分支使用，例如下面的代码：

```tsx
// 正确的
type ArrayItemType<T> = T extends Array<infer K> ?  null : K;

// 错误的，K不能在false分支使用
type ArrayItemType<T> = T extends Array<infer K> ?  null : K;
```

## 类型谓词

TypeScript的类型谓词是一个检查类型的函数，用于检查一个输入值是否为某个特定类型。它的语法如下：

```tsx
function isString(type: unknown): type is string {
    return typeof type === 'string'
}
```

类型谓词的作用在于它能够检查一个值是否为某个特定类型，并且如果检查成功，返回true，否则返回false。它可以用于类型检查，以确保代码的正确性。

```tsx
function fn(x: unknown) {
    if (isString(x)  /*判断x是否是string类型*/ ) {
        x.toUpperCase();
    }
}
```

## 非空断言

Typescript还有个很实用的操作符——非空断言，它想到于告诉Typescript这个变量肯定不会是undefined或null。

它的用法是变量后面跟个`!`符号。

例如：

```tsx
let a: string|null;
let b:string = a // error，因为a可能null
let c:string = a! // ok
```

也可以用在对象的属性或方法上，就像可选链一样：

```tsx
a!.b // a不为null或undefined
```

例如在vue的$refs中，有时我们明确知道`ref`引用了某个dom节点，例如`this.$refs.linkRef`，但是typescript会认为`this.$refs.linkRef` 有可能为undefined，这个时候就可以用非空断言，如`this.$refs!.linkRef` ，当然另一种写法是用可选链，因为在某些情况下（例如页面跳转）引用的dom节点确实会为空。

## 确定赋值断言

`TypeScript` 的确定赋值断言，允许在实例属性和变量声明后面放置一个 `!` 号，从而告诉 `TypeScript` 该属性会被明确地赋值。

```tsx
let name!: string;
```

举个例子：

```tsx
let a:string;

fn()

a.toLocaleLowerCase()

function fn(){
    a = ''
}
```

在这个例子中，我们已经知道变量a已经被赋值，但是typescript没有检测到，因此我们需要用确定赋值断言明确地告诉

## 实用类型

typescript提供一些全局范围可用的类型，可以简化我们的一些操作。

<https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type>
