Skip to content

类型查询

js/ts-typeof

typescript
typeof 42; // "number"
typeof true; // "boolean"
typeof 'hello'; // "string"
typeof {}; // "object"
typeof []; // "object"
typeof null; // "object" (这是 JavaScript 中的一个错误)
typeof function() {}; // "function"
typeof undefined; // "undefined"

ts-typeof

在 TypeScript 中, typeof 可以用作类型查询,这个时候, typeof返回的是一个TypeScript类型

typescript
const str = "yyds";

const obj = { name: "yyds" };

const nullVar = null;
const undefinedVar = undefined;

const func = (input: string) => {
  return input.length > 10;
};

type Str = typeof str; // "yyds"
type Obj = typeof obj; // { name: string; }
type Null = typeof nullVar; // null
type Undefined = typeof undefined; // undefined
type Func = typeof func; // (input: string) => boolean
typescript
import { someValue } from './some-module';

const valueType = typeof someValue; // 得到 someValue 的类型

typeof操作符在作用于字面量值时,会直接返回该值的字面量类型。

typescript
const func = (input: string) => {
  return input.length > 10;
};

// func2 的类型是 (input: string) => Boolean
const func2: typeof func = (name: string) => {
  return name === "vue";
};

字面量类型

在 TypeScript 中,字面量类型(Literal Types)是一种特殊的类型,它们指的是字面量本身的类型。

字符串字面量类型

typescript
type Direction = 'up' | 'down' | 'left' | 'right';

let dir: Direction = 'up'; // 正确
dir = 'go'; // 错误,不是'up'、'down'、'left'或'right'之一

数字字面量类型

typescript
type Species = 1 | 2 | 3;

function getAnimalName(species: Species) {
  switch (species) {
    case 1:
      return 'Dog';
    case 2:
      return 'Cat';
    case 3:
      return 'Rabbit';
    default:
      // 这里永远不会执行,因为species的取值范围已被限定
      return 'Unknown';
  }
}

布尔字面量类型

typescript
type BooleanLike = true | false | 0 | 1 | '' | 'false';

const isTrue: BooleanLike = true; // 正确
const isFalse: BooleanLike = 0; // 正确
const notBoolean: BooleanLike = 'not boolean'; // 错误

数组字面量类型

typescript
const arr1 = [1, 2, 3];
type ArrayLike = typeof arr1; // number[]

const arr2 = ['a', 'b', 'c'];
type ArrayOfStrings = typeof arr2; // string[]

函数字面量类型

我们可以使用 typeof 操作符来获取函数字面量的类型: 注意, 函数字面量类型捕获了函数的整个结构,包括参数个数、参数类型、返回值类型等。

typescript
const add = (a: number, b: number) => a + b;
type AddFunction = typeof add; // (a: number, b: number) => number

我们还可以将函数字面量类型作为参数类型或返回值类型使用:

typescript
function callWithAdd(fn: typeof add) {
  const result = fn(1, 2);
  console.log(result); // 输出 3
}

const addNumbers: AddFunction = (a, b) => a + b;
callWithAdd(addNumbers); // 正确

callWithAdd((a, b) => `${a} + ${b}`); // 错误,类型不匹配

对象字面量类型

typescript
const obj1 = { x: 1, y: 2 };
type PointLike = typeof obj1; // { x: number, y: number }

const obj2 = { point: obj1 };
type PointWrapper = typeof obj2; // { point: { x: number, y: number } }

PS. 对象-函数

函数

在 TypeScript 中,函数和对象都不被视为字面量值,但是它们可以通过特殊的方式构造出字面量类型。

typescript
const myFunc = function() {
  // ...
}
// myFunc 是一个变量,而不是字面量

但是,我们可以使用 typeof 操作符从函数字面量表达式中推导出字面量类型

typescript
type MyFuncType = typeof function() {};
// MyFuncType 被推导为 () => void 类型

这种方式推导出的类型就是函数字面量类型。

对象

我们同样可以使用 typeof 从对象字面量中推导出对象字面量类型:

typescript
type Obj = typeof { x: 1 };
// Obj 被推导为 { x: number } 类型

什么是字面量

在 TypeScript 中,字面量(Literal)指的是一个固定或者确定的值。理解字面量的关键在于它是一个单一、不可变的值, 而不是一个可以持有多种值的变量或对象。

TypeScript 中的字面量主要包括以下几种:

  1. 字符串字面量(String Literal)
    1. 使用单引号或双引号括起来的字符序列,如 'hello'、"world"。
  2. 数字字面量(Number Literal)
    1. 一个不可变的数值,如 42、3.14。
  3. 布尔字面量(Boolean Literal)
    1. true 或 false。
  4. null 和 undefined
    1. 特殊的非值。
  5. 对象字面量(Object Literal)和数组字面量(Array Literal)
    1. 使用 {} 或 [] 语法直接定义的对象和数组,如 { x: 1, y: 2 }、[1, 2, 3]
  6. 函数字面量(Function Literal)
    1. 一个匿名的内联函数定义,如 () => { /* ... */ }

字面量本身就是一个确定的、不可变的值,所以它们在TypeScript类型系统中扮演着非常重要的角色。我们可以使用字面量来定义更精确、更严格的类型。 例如,我们可以使用字符串字面量联合类型来限制一个变量只能取某几个字符串值:

typescript
type Direction = 'up' | 'down' | 'left' | 'right';
let d: Direction = 'up'; // ok
d = 'go'; // 错误,不是'up'、'down'、'left'或'right'之一

或者使用对象字面量类型来描述对象的确切形状:

typescript
const obj = { x: 1, y: 2 };
type Point = typeof obj; // { x: number, y: number }

类型守卫

TypeScript 中的类型守卫(Type Guards)可以让我们**在特定的代码分支中缩小类型范围,**从而获得更高的类型安全性。

类型谓词 is

类型谓词是一个返回值为类型谓词的函数, 用于缩小类型范围。 类型谓词的形式为 parameterName is Type

typescript
function isString(val: any): val is string {
  return typeof val === 'string';
}

isString 函数称为类型守卫,在它的返回值中,我们不再使用 boolean 作为类型标注,而是使用 val is string 这么个奇怪的搭配.

  1. val : 函数的某个参数
  2. is string : 即 is 关键字 + 预期类型,即如果isString这个函数成功返回为 true,那么 is 关键字前这个入参的类型,就会被这个类型守卫调用方后续的类型控制流分析收集到
typescript
function isString(input: unknown): input is string {
  return typeof input === "string";
}

function foo(input: string | number) {
  if (isString(input)) {
    // 正确了
    (input).replace("666", "999")
  }
  if (typeof input === 'number') { }
  // ...
}

下面这种写法就会Error,因为 isString 这个函数在另外一个地方,内部的判断逻辑并不在函数 foo 中。这里的类型控制流分析做不到跨函数上下文来进行类型的信息收集

typescript
function isString(input: unknown): boolean {
  return typeof input === "string";
}

function foo(input: string | number) {
  if (isString(input)) {
    // 类型“string | number”上不存在属性“replace”。
    (input).replace("666", "999")
  }
  if (typeof input === 'number') { }
  // ...
}

类型守卫函数不会检查逻辑

typescript
// 1. 我们假定isString 参数input为number类型
function isString(input: unknown): input is number {
  return typeof input === "string";
}

function foo(input: string | number) {
  // 2. 这里程序会自动默认input是number类型,并不会执行逻辑检查
  if (isString(input)) {
    // 报错,在这里变成了 number 类型
    (input).replace("666", "999")
  }
  if (typeof input === 'number') { }
  // ...
}

类型守卫类似于类型断言

其实类型守卫有些类似于类型断言,但类型守卫更宽容,也更信任你一些。你指定什么类型,它就是什么类型。 除了使用简单的原始类型以外,我们还可以在类型守卫中使用对象类型、联合类型等。

typescript
export type Falsy = false | "" | 0 | null | undefined;

export const isFalsy = (val: unknown): val is Falsy => !val;

// 不包括不常用的 symbol 和 bigint
export type Primitive = string | number | boolean | undefined;

export const isPrimitive = (val: unknown): val is Primitive => ['string', 'number', 'boolean' , 'undefined'].includes(typeof val);

in 类型守卫

TypeScript 内置了一些类型守卫,如 in、 typeof、instanceof, 等,可以直接使用。 in 用于判断一个属性是否存在于对象中。

typescript
interface Circle {
  radius: number;
}

interface Square {
  sideLength: number;
}

function getArea(shape: Circle | Square) {
  if ('radius' in shape) {
    // 在这个代码分支中, shape 被自动缩小为 Circle 类型
    return Math.PI * shape.radius ** 2;
  } else {
    // 在这个代码分支中, shape 被自动缩小为 Square 类型
    return shape.sideLength ** 2;
  }
}

typeof 类型守卫

这是最常用的类型守卫之一。它可以帮助我们缩小变量的类型范围。

typescript
function printValue(val: number | string) {
  if (typeof val === 'string') {
    // 在这个代码分支中, val 被自动缩小为 string 类型
    console.log(val.toUpperCase());
  } else {
    // 在这个代码分支中, val 被自动缩小为 number 类型
    console.log(val.toFixed(2));
  }
}

instanceof 类型守卫

用于判断一个实例是否属于某个类的实例。

typescript
class User {
  constructor(public name: string) {}
}

function printUser(user: User | string) {
  if (user instanceof User) {
    // 在这个代码分支中, user 被自动缩小为 User 类型
    console.log(user.name);
  } else {
    // 在这个代码分支中, user 被自动缩小为 string 类型
    console.log(user.toUpperCase());
  }
}

类型断言守卫

这不是一种真正的类型守卫,但它可以用于手动缩小类型范围。需要小心使用,因为错误的断言会导致运行时错误。

typescript
interface Circle {
  radius: number;
}

function getCircleArea(shape: Circle | any) {
  if (typeof shape.radius === 'number') {
    // 使用类型断言手动缩小类型
    const circle = shape as Circle;
    return Math.PI * circle.radius ** 2;
  }
  // 处理其他情况
}