类型操作符

# 类型操作符

# keyof类型操作符

对一个对象类型使用keyof操作符,会返回该对象属性名组成的一个字符串或者数字字面量的联合。这个例子中的类型P就等同于 "x"|"y":

type Point = {x:number,y:number}

type P = keyof Point

但是如果这个类型有一个string或者number类型的索引签名,keyof则会直接返回这些类型

这里返回的是key值的
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
// type A = number

type Mapish = { [k: string]: boolean };
type M = keyof Mapish
// type M = string | number 

在这个例子中,M是string|number,这是因为javascript对象中的属性名会被强制转为一个字符串,所以obj[0]和obj["0"]是一样的.

# 数字字面量联合类型

在一开始我们也说了,keyof也可能返回一个数字字面量的联合类型,那什么时候返回数字字面量的联合类型呢?看代码

const NumberIcObject={
  [1]:1,
  [2]:2,
  [3]:3
}
type result = keyof NumberIcObject
//type result = 1|2|3

# Symbol

其实TypeScript 也支持symbol类型的属性名

const sym1 = Symbol();
const sym2 = Symbol();
const sym3 = Symbol();

const symbolToNumberMap = {
  [sym1]: 1,
  [sym2]: 2,
  [sym3]: 3,
};
type Ks = keyof typeof symbolToNumberMap 
// typeof sym1 | typeof sym2 | typeof sym3

这也就是为什么当我们在泛型中像下面的例子中使用会报错的原因:

function useKey<T,K extends keyof T>(o: T, k: K){
  let name:string = k;
  // Type 'string | number | symbol' is not assignable to type 'string'.
}

如果你确定只使用字符串类型的属性名,你可以这样写:

function useKey<T, K extends keyof T>(o: T, k: K) {
  var name: string | number | symbol = k;
}

# 类和接口

对类使用keyof:

class Person{
  name:'冴羽'
}

type result = keyof Person;
// type result = 'name';

class Person{
  [1]:string = '冴羽'
}

type result = keyof Person;
// type result = 1;

对接口使用keyof:

interface Person:{
  name:string
}

type result = keyof Person
type result = 'name'

# 实战

我们希望获取一个对象给定属性名的值,为此,我们需要确保我们不会获取 obj 上不存在的属性。所以我们在两个类型之间建立一个约束:

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");

// Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

# typeof类型操作符

JavaScript 本身就有 typeof 操作符,你可以在表达式上下文中(expression context)使用:

// Prints "string"
console.log(typeof "Hello world");

而 TypeScript 添加的 typeof 方法可以在类型上下文(type context)中使用,用于获取一个变量或者属性的类型。


let s = "hello";
let n: typeof s;
// let n: string

如果仅仅用来判断基本的类型,自然是没什么太大用,和其他的类型操作符搭配使用才能发挥它的作用。

举个例子:比如搭配 TypeScript 内置的 ReturnType<T>。你传入一个函数类型,ReturnType<T> 会返回该函数的返回值的类型:

type Predicate = (x: unknown) => boolean;
type K = ReturnType<Predicate>;
// type K = boolean

如果我们直接对一个函数名使用 ReturnType ,我们会看到这样一个报错:

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<f>;

// 'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?

这是因为值(values)和类型(types)并不是一种东西。为了获取值 f 也就是函数 f 的类型,我们就需要使用typeof:

function f() {
  return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
                    
// type P = {
//    x: number;
//    y: number;
// }

# 限制(Limitations)

TypeScript 有意的限制了可以使用 typeof 的表达式的种类。

在 TypeScript 中,只有对标识符(比如变量名)或者他们的属性使用 typeof 才是合法的。这可能会导致一些令人迷惑的问题:

// Meant to use = ReturnType<typeof msgbox>
let shouldContinue: typeof msgbox("Are you sure you want to continue?");
// ',' expected.

我们本意是想获取 msgbox("Are you sure you want to continue?") 的返回值的类型,所以直接使用了 typeof msgbox("Are you sure you want to continue?"),看似能正常执行,但实际并不会,这是因为 typeof 只能对标识符和属性使用。而正确的写法应该是:

ReturnType<typeof msgbox>

# 对对象使用 typeof

let Obj = {
  name:'x',
  age:12
}
type result = typeof Obj

// type result = {
//   name:string,
//   age:number
// }

# 对函数使用 typeof

function identity<Type>(arg:Type):Type
type result = typeof identity

// type result = <Type>identity(arg:Type)=>Type

# 对 enum 使用 typeof

在 TypeScript 中,enum 是一种新的数据类型,但在具体运行的时候,它会被编译成对象。

enum UserResponse {
  No = 0,
  Yes = 1,
}

对应编译的 JavaScript 代码为:

var UserResponse;
(function (UserResponse) {
    UserResponse[UserResponse["No"] = 0] = "No";
    UserResponse[UserResponse["Yes"] = 1] = "Yes";
})(UserResponse || (UserResponse = {}));

如果我们打印一下 UserResponse:

console.log(UserResponse);

// [LOG]: {
//   "0": "No",
//   "1": "Yes",
//   "No": 0,
//   "Yes": 1
// } 

而如果我们对 UserResponse 使用 typeof:

type result = typeof UserResponse;

// ok
const a: result = {
      "No": 2,
      "Yes": 3
}

result 类型类似于:

// {
//	"No": number,
//  "YES": number
// }

不过对一个 enum 类型只使用 typeof 一般没什么用,通常还会搭配 keyof 操作符用于获取属性名的联合字符串:

type result = keyof typeof UserResponse;
// type result = "No" | "Yes"

# in 操作符

in操作符用于遍历目标类型的公开属性名。类似 for .. in 的机制。

# 使用于枚举类型

我们可以像下面这样使用枚举类型:

type Property = 'name' | 'age' | 'phoneNum';

type PropertyObject = {
  [key in Property]:string 
}
// type PropertyObject = {
//     name: string;
//     age: string;
//     phoneNum: string;
// }

# is操作符

is 操作符用于TS的类型谓词中,是实现TS类型保护的一种方式

function doSometing(value: string | number) {
    if (typeof value === 'string') {
        // TS 可以识别这个分支中 value 是 string 类型的参数(这就叫类型保护)
        // do something
    } else {
        // TS 可以识别这个分支中 value 是 number 类型的参数
        // do something
    }
}

除去上面这种方式以外,我们可以使用TS的类型谓词来实现:

/**
 * 此函数用于判断参数 value 是不是 string 类型
 * 
 * 由于返回类型声明了类型谓词,可以帮助TS在代码分支中进行类型保护(默认返回 boolean 类型是没办法做到的)
 **/
function isString(value: any): value is string {
    return typeof value === 'string';
}

function doSometing(value: string | number) {
    if (isString(value)) {
        // TS 可以识别这个分支中 value 是 string 类型的参数(这就叫类型保护)
    } else {
        // TS 可以识别这个分支中 value 是 number 类型的参数
    }
}

上次更新: 1 小时前