红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 前端学习笔记
  3. 正文

TypeScript 学习笔记 1:类型

2026年2月11日 13点热度 0人点赞 0条评论

在系统学习 TypeScript 前,需要先部署开发环境,具体可以查看TypeScript 简易指南。

基本类型

TypeScript 与 JavaScript 一样,常见的基本类型有:

let name: string = "张三";
console.log(name.toUpperCase());
​
let age: number = 42;
console.log(age.toFixed(2));
​
let isOk: boolean = true;
console.log(isOk.valueOf());

这里的用注解的方式进行类型声明,这并不是必须的,在这里,TypeScript 的类型检查器可以很容易地推断出局部变量的类型,即使它们没有使用注解标记类型。

数组

数组类型可以使用<type>[]表示,比如:

let list: number[] = [1, 2, 3];
console.log(list.length);

也可以使用泛型Array<type>表示:

let list2: Array<number> = [1, 2, 3];
console.log(list2.length);

它们是等效的。

any

any 类型表示这可以是任何一个类型,换言之,TypeScript 的类型检查器将失效,比如:

let obj: any = { x: 0 };
​
console.log(obj.x);
console.log(obj.y); //undefined
console.log(obj.toUpperCase()); // TypeError: obj.toUpperCase is not a function

因为这里obj被声明为any,所以可以使用任意的方式使用它,都不会报错(无类型检查)。

这是有意义的,对于某些从 JavaScript 移植到 TypeScript 的项目,或者某个需要编写繁杂的类型定义才能通过检查的变量,你都可以使用any简单地绕过这些类型检查。

正如前面所说,TypeScript 会在没有显式指定类型时,尽可能地自动推断出可能的类型。但某些时候无法通过上下文推断出类型,此时就会将目标类型视为any。这在大多数时候不是问题,但如果你认为这样不合适,应当在这样的情况下人为补全类型说明,而非使用这种行为。可以在这种情况下使用编译器标识noImplicitAny关闭这一行为。

函数

参数类型

function printMsg(msg: string){
    console.log(msg);
}
​
printMsg("hello world");

TypeScript 支持可选参数:

function total(a: number, b?: number) {
  if (b === undefined) {
    b = 0;
  }
  return a + b;
}
​
console.log(total(1));
console.log(total(1, 2));

这里b?表示这个参数可传可不传,如果不传的时候这里接收到的就是undefined,因此函数中需要检查对应的实参值是否是undefined,否则会报错。

返回值类型

function randomNumber(): number {
  return Math.random();
}
​
console.log(randomNumber());

返回值类型声明并非必须的,通常 TypeScript 都可以通过return的类型判断出函数的返回值类型。

对象类型

可以简单地使用字面量的方式定义目标对象的类型:

function printCoord(pt: { x: number; y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
​
printCoord({ x: 100, y: 100 });

对象类型中的属性类型是可选的,如果没有指定,默认是any。

可选属性

function printName(obj: { first: string; last?: string }) {
  if (obj.last !== undefined) {
    console.log(obj.first + " " + obj.last);
  } else {
    console.log(obj.first);
  }
}
​
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

与可选参数类似,这里同样需要判断可选属性是否是undefined。

联合类型

在 TypeScript 中,可以使用联合(Union)的方式定义一个拥有多个值/类型的类型。

一种 ID,可以是数字或字符串:

type ID = number | string;
function getID(id: ID): string {
  return `ID: ${id}`;
}
​
console.log(getID(123));
console.log(getID("abc"));

自定义 bool 类型:

type MyBoolean = true | false;
function isTrue(value: MyBoolean): string {
  return value ? "真" : "假";
}
​
console.log(isTrue(true));
console.log(isTrue(false));

枚举类型:

type WindowStates = "open" | "closed" | "minimized";
function getWindowState(state: WindowStates): string {
  return `当前窗口状态为:${state}`;
}
​
console.log(getWindowState("open"));
console.log(getWindowState("closed"));
console.log(getWindowState("minimized"));

字符串或数组:

function wrapInArray(obj: string | string[]) {
  if (typeof obj === "string") {
    return [obj];
  }
  return obj;
}
​
console.log(wrapInArray("hello"));
console.log(wrapInArray(["hello"]));

可以看到,TypeScript 的类型与传统的强类型语言中的类型完全不同,它的类型可以被看作一个类型的集合,因此可以是类型 A 也可以是类型 B。

缩小范围

使用了联合类型,意味着接收到的类型可能是其中的任何一个类型,换言之,你只能使用这些可能类型都拥有的属性或方法。如果你要使用特定的方法,就需要先通过类型判断缩小范围:

function printId(id: number | string) {
  if (typeof id === "string") {
    id = id.toUpperCase();
  }
  console.log("Your id is: " + id);
}
​
printId(101);
printId("abc-123"); // Your id is: ABC-123

类型别名

可以将任意类型使用类型别名表示:

type Student = { name: string; age: number };
function printStudent(student: Student) {
  console.log("Name: " + student.name);
  console.log("Age: " + student.age);
}
​
printStudent({ name: "张三", age: 18 });

interface

除了使用type,interface同样可以用于定义类型别名:

interface Student2 {
  name: string;
  age: number;
}
function printStudent2(student: Student2) {
  console.log("Name: " + student.name);
  console.log("Age: " + student.age);
}
​
printStudent2({ name: "张三", age: 18 });

type or interface

与类型别名不同,interface可以重复定义的方式进行扩展,比如:

interface Student3 {
  name: string;
}
​
interface Student3 {
  age: number;
}
​
function printStudent3(student: Student3) {
  console.log("Name: " + student.name);
  console.log("Age: " + student.age);
}

看起来多少有点奇怪,如果用类似的方式使用type定义,就会报错。

当然这样的写法并不常见,更常见的是通过继承的方式扩展接口,比如:

interface Person {
  name: string;
  age: number;
}
​
interface Teacher extends Person {
  teach(): void;
}

如果用类似的方式扩展type:

type Person2 = { name: string; age: number };
type Teacher2 = Person2 & { teach(): void };

类型断言

在浏览器中使用document.getElementById获取到的 DOM 元素的类型都是HTMLElement,但如果我们直到目标 DOM 的具体类型:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

这有点像 Java 中的强制类型转换,我们缩小了类型范围。

除了这种写法,还可以使用类似泛型的写法:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

为了避免出现奇怪的写法,类型断言仅能将类型变得更具体或更宽泛,不能将其用于两个没有关系的类型:

const x = "hello" as number;

这里 string 和 number 是没有任何关系的两个类型,因此不能使用类型断言进行转换。

但有时候你或许会希望绕过这种限制,强行将类型指定为某个类型,可以通过下面的方式:

const a = expr as any as T;

这里先将类型指定为any,再指定为目标类型。

字面量类型

type WindowStates2 = "open" | "closed" | "minimized";
function setWindowState(state: WindowStates2) {
  console.log(state);
}
​
setWindowState("open");
setWindowState("closed");

字面量类型可以和普通类型结合使用:

type Configuration = {
  readonly databaseURL: string;
  readonly maxConnections: number;
};
function setConfig(configuration: Configuration | "auto") {
  let databaseURL: string;
  let maxConnections: number;
  if (configuration === "auto") {
    databaseURL = "https://example.com/db";
    maxConnections = 42;
  } else {
    databaseURL = configuration.databaseURL;
    maxConnections = configuration.maxConnections;
  }
  console.log(`databaseURL: ${databaseURL}`);
  console.log(`maxConnections: ${maxConnections}`);
}
​
setConfig({ databaseURL: "https://example.com/mydb", maxConnections: 111 });
setConfig("auto");

字面量推断

对象的属性可能在任意时候被修改,比如:

const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

因此这里 TypeScript 会将对象obj的counter属性的类型推断为number,而不是字面量类型0。

这在某些时候会出现问题,比如:

function handleRequest(url: string, method: "GET" | "POST"){
    console.log(`URL: ${url}`);
    console.log(`Method: ${method}`);
}
​
handleRequest("https://example.com", "GET");

这样没有任何问题,但是如果使用对象属性传参:

function handleRequest(url: string, method: "GET" | "POST"){
    console.log(`URL: ${url}`);
    console.log(`Method: ${method}`);
}
const request = {
    url: "https://example.com",
    method: "GET"
}
handleRequest(request.url, request.method); // 类型 “string” 的参数不能赋给 类型 “"GET" | "POST"” 的参数。

这里会报错,报错信息见注释。可能这里的报错会让人困惑,明明此时request.method的值就是GET。

但就像前面所说,对象的属性值是可以随时修改的,所以这里 TypeScript 的类型推断仅仅会人为它是string类型,而不认为它的值固定在GET。

可以强行指定类型的方式解决这个问题:

handleRequest(request.url, request.method as "GET");

表示“我很确定此时的值就是GET”。

或者:

const request = {
    url: "https://example.com",
    method: "GET"
} as const;
handleRequest(request.url, request.method);

这表示我确定request的属性值不会再改变,因此它的类型应当是字面量类型。

strictNullChecks

开启编译器选项strictNullChecks后,当类型可能是null或undefined时,需要在判断排除后才能调用相应的属性/方法:

function liveDangerously(x?: number | null) {
  if (x !== null && x !== undefined) {
    console.log(x.toFixed());
  }
}
​
liveDangerously(null);
liveDangerously(undefined);
liveDangerously();
liveDangerously(1);

如果确定目标类型不是null或undefined,可以:

function liveDangerously2(x?: number | null) {
  console.log(x!.toFixed());
}

这里的x!是一种断言,表示x必然存在(不是null或undefined)。

泛型

一个典型用途是定义特定类型元素的数组,比如字符串数组:

type StrArray = Array<string>;
function  printStrArray(arr: StrArray) {
  arr.forEach(item => {
    console.log(item);
  });
}
​
printStrArray(["hello", "world"]);

数字数组:

type NumberArray = Array<number>;
function  getTotal(arr: NumberArray) {
  return arr.reduce((acc, cur) => acc + cur);
}
​
console.log(getTotal([1, 2, 3, 4, 5]));

自定义类型也可以使用泛型:

interface Result<T> {
  data: T;
  errorMsg: string | undefined;
  success: boolean;
  toString: () => string;
  isSuccess: () => boolean;
  setData: (data: T) => void;
  getData: () => T | null;
}
​
class MyResult<T> implements Result<T> {
  data: T;
  errorMsg: string | undefined;
  success: boolean;
  constructor(data: T, success: boolean, errorMsg?: string) {
    this.data = data;
    this.success = success;
    this.errorMsg = errorMsg;
  }
  toString() {
    return `data: ${this.data}, success: ${this.success}, errorMsg: ${this.errorMsg}`;
  }
​
  // 实现 isSuccess 方法
  isSuccess(): boolean {
    return this.success;
  }
​
  // 实现 setData 方法
  setData(data: T): void {
    this.data = data;
  }
​
  // 实现 getData 方法
  getData(): T | null {
    return this.success ? this.data : null;
  }
}
​
const result: Result<number> = new MyResult<number>(123, true);
console.log(result.toString());

结构化类型系统

TypeScript 使用一种“鸭子类型”进行验证,也就是说只要接收的类型与定义的类型能匹配,就可以通过类型验证,并不需要接收的类型被显式定义为目标类型:

interface Point{
    x: number;
    y: number;
}
​
function printPoint(point: Point) { 
    console.log(`x: ${point.x}, y: ${point.y}`);
}
​
printPoint({x: 1, y: 2});

值得注意的是,如果是字面量,就需要严格匹配:

printPoint({x: 1, y: 2});
// printPoint({x: 1});
// printPoint({x: 1, y: 2, z: 3});

这里字面量无论是少属性还是多属性都无法通过验证。

反之,如果是对象,就比较宽泛,只要目标对象能满足验证条件即可:

const point3 = { x: 12, y: 26, z: 89 };
printPoint(point3); // 打印 "12, 26"

这里多属性依然能通过验证。

这样设计是说的通的,毕竟字面量仅仅用于这里,并不会在其他地方复用,严格要求很合理。

对于类,也是一样的,满足验证条件即可,并不需要显式让类实现目标类型:

class MyPoint{
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}
​
const point = new MyPoint(1, 2);
printPoint(point);

看一个更极端的例子:

interface Empty{}
​
function printEmpty(empty: Empty) { 
    console.log("empty");
}
​
printEmpty({x: 1, y: 2});

因为类型Empty没有任何属性,因此这里任何对象都能满足类型检查。

再次强调,在鸭子类型中,只要两个类型长得像,它们就可以被看作同一种类型,即使没有在任何地方声明它们之间的连系:

class Car {
  drive() {
    // hit the gas
  }
}
class Golfer {
  drive() {
    // hit the ball far
  }
}
// No error?
let w: Car = new Golfer();

The End.

参考资料

  • TypeScript手册

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: ts
最后更新:2026年2月11日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号