头图

教练,我想练TypeScript类型体操!!!

前言

大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。 欢迎点赞收藏关注一键三连!!!

TypeScript已经成为构建大型应用的行业标准,众多组织选择它作为主要的应用开发语言。

本教程将作为你学习TypeScript的入门指南。它旨在满足从初学者到高级用户的各个阶段的学习者需求,教授基础和高级的TypeScript概念,是任何想要深入了解TypeScript的人的有用资源。

本指南的目标不是成为穷尽资源,而是成为一个简洁且实用的参考。它将TypeScript的精髓提炼成易于消化的格式。

无论你是刚开始的新手,想要巩固知识的中级学习者,还是需要快速复习的高级用户,本指南都旨在满足你的TypeScript学习需求。

通过仔细阅读本教程并实践其中的例子,你应该能够构建健壮、可扩展且可维护的TypeScript应用程序。我们将涵盖关键的TypeScript概念,如类型、函数、类、接口、泛型等。

先决条件

在开始本指南之前,你应该具备基本的JavaScript知识。熟悉面向对象编程概念,如类、接口和继承也是推荐的。

但如果你对这些概念还不熟悉,不用担心——我们将在本指南中详细覆盖它们。

本指南适合谁?

本指南适合任何想要学习TypeScript的人。无论你是初学者、中级学习者还是高级用户,本指南旨在满足你的TypeScript学习需求。

它也是想要重温TypeScript技能的人的实用参考。

TypeScript与JavaScript

TypeScript是JavaScript的静态类型超集,旨在增强大规模应用的开发。

它引入了可选的静态类型接口JavaScript中,与C#Java等语言相似。TypeScript代码被转译成纯JavaScript,确保在各种JavaScript环境中的兼容性。

虽然TypeScript和JavaScript可以在相同的环境中运行,但它们展现出关键的不同。主要的区别在于TypeScript是静态类型的,提供了类型安全性,而JavaScript是动态类型的。

让我们通过一个例子来探索JavaScript和TypeScript之间的差异:

// JavaScript
function add(a, b) {
  return a + b;
}

// 调用函数
add(1, 2); // 返回:3

在上面的JavaScript示例中,add函数接收两个参数ab,并返回它们的和。函数用参数12调用,得到3。请注意,函数参数没有用类型注释,这是JavaScript中的典型做法。

现在,让我们看看如何用TypeScript编写相同的函数:

// TypeScript
function add(a: number, b: number): number {
  return a + b;
}

// 调用函数
add(1, 2); // 返回:3

在TypeScript版本中,我们用number类型注释了参数ab。我们还指定了函数返回一个number

这是JavaScript和TypeScript之间的一个关键区别。TypeScript强制执行类型安全,意味着它在编译时检查值的类型,并在它们与预期类型不匹配时抛出错误。

这个特性有助于在开发过程中早期捕获错误,使TypeScript成为大型应用的流行选择。

TypeScript和JavaScript都是用于各种应用的强大语言。让我们总结它们的关键区别:

特性TypeScriptJavaScript
类型系统静态类型(编译时检查类型)动态类型(运行时检查类型)
超集是,TypeScript是JavaScript的超集
编译需要编译(或转译)成JavaScript不需要编译
面向对象特性包括接口、泛型和装饰器等高级面向对象特性通过原型支持面向对象,不原生支持接口或泛型
工具提供更好的工具,如自动补全、类型检查和源图支持基本工具
社区和生态系统新兴的,较小的社区和生态系统自1995年以来,拥有庞大的社区和庞大的库和框架生态系统
学习曲线由于额外的特性而更陡峭通常对初学者更容易
用例通常用于大型代码库,类型检查和自动补全的优势最为明显用于客户端和服务器端开发,可以在浏览器中原生执行

TypeScript的优势

TypeScript提供了几个优于JavaScript的优势:

  1. 改进的工具:TypeScript的静态类型启用了更好的工具支持。像自动补全、类型推断和类型检查这样的功能使得开发过程更高效、更愉快。
  2. 更好的文档:TypeScript代码库通常更容易理解和维护。类型注释作为隐式文档,使得理解函数期望和返回的值类型更容易。
  3. 高级特性:TypeScript支持像装饰器async/await这样的高级JavaScript特性,它还引入了JavaScript中不可用的特性,如接口枚举元组
  4. 重构:TypeScript的工具使得重构更大的代码库更安全、更可预测。你可以自信地进行大规模更改。
  5. 逐步采用:TypeScript是JavaScript的超集,这意味着你可以逐步在项目中采用TypeScript。你可以从将.js文件重命名为.ts开始,然后你可以逐步添加类型注释。
  6. 社区和生态系统:TypeScript拥有不断增长的社区和生态系统。许多流行的JavaScript库,如React和Angular,都有TypeScript定义,这使得在TypeScript项目中使用它们更容易。

代码生成

TypeScript代码不是浏览器或Node.js原生理解的,所以它需要被转译成JavaScript。这个转译过程由TypeScript编译器(tsc)处理,它读取TypeScript代码并生成等效的JavaScript代码。

要转译一个TypeScript文件,你可以使用tsc命令后跟文件名:

$ tsc index.ts

此命令将index.ts文件转译成一个相同目录下的index.js文件。生成的JavaScript代码可以在任何JavaScript环境中执行,比如浏览器或Node.js。

监听文件变化

在积极开发过程中,让你的TypeScript代码在你进行更改时自动重新编译是有益的。TypeScript编译器提供了--watch选项用于此目的:

$ tsc index.ts --watch

使用此命令,编译器将监视index.ts文件,并在检测到更改时自动重新编译它。

配置TypeScript编译器

对于更大的项目,通常有一个配置文件tsconfig.json来管理编译器选项。此文件允许你指定根级文件和编译项目所需的编译器选项。

以下是tsconfig.json文件的一个示例:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

在此配置中,compilerOptions对象包含编译器的选项。target选项指定ECMAScript目标版本,module选项设置模块系统,strict选项启用广泛的类型检查行为,outDir选项指定编译的JavaScript文件的输出目录。

includeexclude选项用于指定要编译和忽略的文件。

要基于tsconfig.json文件编译项目,你可以不带任何参数运行tsc命令:

$ tsc

此命令将根据tsconfig.json文件中指定的选项编译项目中的所有TypeScript文件。

TypeScript基础

在本节中,我们将介绍TypeScript的基础知识。你将看到更多TypeScript静态类型的示例,并了解其工具和错误检查。

安装

在我们深入TypeScript之前,你需要确保你的系统上安装了Node.js。Node.js是一个运行环境,允许你在浏览器之外运行JavaScript。你可以从官方网站下载Node.js。

Node.js安装完成后,你可以使用Node.js附带的Node包管理器(npm)安装TypeScript。

打开终端并运行以下命令:

npm install -g typescript

此命令在你的系统上全局安装TypeScript。你可以通过运行tsc命令来确认安装,tsc代表TypeScript编译器:

tsc --version

此命令应该显示你安装的TypeScript版本。

现在TypeScript安装好了,我们准备好开始TypeScript之旅了!

配置

太好了!现在我们已经安装了TypeScript,让我们讨论另一个重要事项:配置。对于更大的项目,通常有一个配置文件tsconfig.json来管理编译器选项。此文件允许你指定根级文件和编译项目所需的编译器选项。

. 剩余参数:TypeScript支持剩余参数,允许你将任意数量的参数传递给函数。

示例:

function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

let total: number = sum(1, 2, 3, 4, 5); // total是15

在这个示例中,sum函数接受任意数量的参数,并返回它们的和。

TypeScript中的类和对象

类是面向对象编程(OOP)的基本部分。它们是创建对象的模板,提供状态(成员变量)的初始值和行为(成员函数或方法)的实现。

TypeScript支持类,使用class关键字声明。TypeScript类的一个优势是它们支持面向对象编程(OOP)特性,如继承封装多态

  1. 类声明:在TypeScript中,类使用class关键字声明。

示例:

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): void {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

let john = new Person('John', 25);
john.greet(); // 输出:Hello, my name is John and I am 25 years old.

在这个示例中,Person是一个具有两个属性nameage以及一个方法greet的类。constructor是一个特殊的方法,用于创建和初始化使用类创建的对象。

  1. 继承:TypeScript支持继承,一种基于另一个类创建类的机制,保留相似的实现。继承使用extends关键字实现。

示例:

class Employee extends Person {
  department: string;

  constructor(name: string, age: number, department: string) {
    super(name, age);
    this.department = department;
  }

  greet(): void {
    super.greet();
    console.log(`I work in ${this.department}.`);
  }
}

let jane = new Employee('Jane', 30, 'HR');
jane.greet(); // 输出:Hello, my name is Jane and I am 30 years old. I work in HR.

在这个示例中,Employee是一个扩展了Person的类。它添加了一个新的属性department并覆盖了greet方法。super关键字用于调用父类的对应方法。

  1. 抽象类:抽象类是不能被直接实例化的类。它们被用作其他类的基类,并可能包含必须由派生类实现的抽象方法。

示例:

abstract class Shape {
  abstract area(): number;
}

class Circle extends Shape {
  radius: number;

  constructor(radius: number) {
    super();
    this.radius = radius;
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

let circle = new Circle(5);
console.log(circle.area()); // 输出:78.54

在这个示例中,Shape是一个具有抽象方法area的抽象类。Circle类扩展了Shape并实现了area方法。抽象类适用于定义一组类的共同接口。

  1. 封装:封装是将数据(属性)和操作数据(方法)的方法捆绑到一个称为类的单个单元中。在TypeScript中,封装通过使用publicprivateprotected等访问修饰符实现。

示例:

class Person {
  private name: string;
  protected age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): void {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

let john = new Person("John", 25);
console.log(john.name); // 错误:属性"name"是私有的
console.log(john.age);  // 错误:属性"age"是受保护的

在这个示例中,namePerson类的私有属性,所以它不能从类外部被访问。age是受保护的属性,所以它可以从子类中被访问,但不能从类外部被访问。

  1. 多态:多态是一个对象可以采取多种形式的能力。在TypeScript中,多态通过方法覆盖实现,子类中的方法与超类中的同名方法具有相同的名称和签名。

示例:

class Animal {
  speak(): void {
    console.log('Animal makes a sound');
  }
}

class Dog extends Animal {
  speak(): void {
    console.log('Dog barks');
  }
}

let animal: Animal = new Dog();
animal.speak(); // 输出:Dog barks

在这个示例中,Animal是一个具有speak方法的基类。Dog是覆盖了speak方法的子类。当将Dog的实例赋给Animal类型的变量时,会调用Dogspeak方法。

  1. 访问修饰符:TypeScript支持publicprivateprotected等访问修饰符。默认情况下,每个成员都是public

示例:

class Animal {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  public getName(): string {
    return this.name;
  }
}

let dog = new Animal('Dog');
console.log(dog.getName()); // 输出:Dog

在这个示例中,nameAnimal类的私有成员。它只能在Animal类内部被访问。getName方法是公共的,所以它可以从类外部被调用。

TypeScript中的接口

接口在TypeScript中是定义代码中合同的强大方式。它们用于类型检查,确保对象符合特定的结构。

通过定义接口,我们可以命名一个特定的变量组合,确保它们始终被用作一组。

  1. 接口声明:接口使用interface关键字声明。

示例:

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

let john: Person = { name: 'John', age: 25 };

在这个示例中,Person是一个描述具有namestring类型)和agenumber类型)的对象的接口。

  1. 可选属性:接口属性可以被标记为可选,使用?

示例:

interface Person {
  name: string;
  age?: number;
}

let alice: Person = { name: 'Alice' };

在这个示例中,agePerson接口中的一个可选属性。即使对象alice没有age属性,它仍然是一个Person

  1. 函数类型:接口也可以描述函数类型。

示例:

interface GreetFunction {
  (name: string, age: number): string;
}

let greet: GreetFunction = function(name: string, age: number): string {
  return `Hello, my name is ${name} and I am ${age} years old.`;
};

在这个示例中,GreetFunction是一个描述接受一个nameage并返回一个string的函数的接口。

  1. 扩展接口:接口可以扩展彼此,创建一个继承基接口成员的新接口。

示例:

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

let myDog: Dog = { name: 'Rex', breed: 'German Shepherd' };

在这个示例中,Dog扩展了Animal,所以Dog既有name也有breed

TypeScript中的枚举

枚举是定义一组命名常数的方式。它们经常用于表示一组相关值,比如一周的天或一年的月份。

TypeScript支持数值枚举和字符串枚举,提供了灵活的方式来定义和使用一组常数。

  1. 数值枚举:数值枚举是定义一组具有数值的命名常数的方式。默认情况下,常数的值从0开始,并随着每个后续常数递增1。

示例:

enum Day {
  Sunday,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}

let today: Day = Day.Monday;

在这个示例中,Day是一个表示一周天的数值枚举。常数SundayMondayTuesday等从0开始被分配数值。

  1. 字符串枚举:字符串枚举是定义一组具有字符串值的命名常数的方式。与数值枚举不同,字符串枚举中的常数的值被初始化为常数名的值。

示例:

enum Month {
  January = 'January',
  February = 'February',
  March = 'March',
  April = 'April',
  May = 'May',
  June = 'June',
  July = 'July',
  August = 'August',
  September = 'September',
  October = 'October',
  November = 'November',
  December = 'December'
}

let currentMonth: Month = Month.June;

在这个示例中,Month是一个表示一年月份的字符串枚举。常数JanuaryFebruaryMarch等被赋予等于它们名字的字符串值。

  1. 计算枚举:枚举可以具有计算值,这些值是用表达式而不是常数值初始化的。这允许在定义常数的值时更加灵活。

示例:

enum Color {
  Red = 1,
  Green = Math.pow(2, 2),
  Blue = Math.pow(2, 3)
}

let color: Color = Color.Green;

在这个示例中,Color是一个具有计算值的枚举。常数RedGreenBlue分别被赋予1、4和8的值,使用Math.pow函数。

  1. 反向映射:TypeScript中的枚举支持反向映射,这意味着你可以从它的值访问常数的名称。这对于调试和日志记录非常有用。

示例:

enum Day {
  Sunday,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}

let dayName: string = Day[1]; // 'Monday'

在这个示例中,Day枚举被用来访问常数的名称。

TypeScript中的泛型

泛型是定义可以与不同数据类型一起使用的函数或类的方式。它们经常用于创建可重用的组件,这些组件可以与不同类型的数据一起工作。

TypeScript支持泛型,允许你编写灵活且类型安全的代码。

让我们来看一些泛型函数和类的示例。

  1. 泛型函数:泛型函数是可以与各种数据类型一起工作的函数。它们使用类型参数定义,这些类型参数是实际将用于函数调用的类型的占位符。

示例:

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

let result1: number = identity<number>(42);
let result2: string = identity<string>('Hello, TypeScript!');

在这个示例中,identity是一个泛型函数,它接受一个类型参数T并返回类型为T的值。类型参数T用于指定参数和返回值的类型。

  1. 泛型类:泛型类是可以与各种数据类型一起工作的类。它们使用类型参数定义,这些类型参数是实际将用于类实例化的类型的占位符。

示例:

class Box<T> {
  value: T;

  constructor(value: T) {
    this.value = value;
  }
}

let box1: Box<number> = new Box<number>(42);
let box2: Box<string> = new Box<string>('Hello, TypeScript!');

在这个示例中,Box是一个泛型类,它接受一个类型参数T并有一个类型为T的属性value。类型参数T用于指定存储在盒子中值的类型。

  1. 泛型约束:泛型约束是一种限制可以与泛型函数或类一起使用的类型的方式。它们使用extends关键字定义,后跟类型或接口,类型参数必须扩展。

示例:

interface Printable {
  print(): void;
}

function printValue<T extends Printable>(value: T): void {
  value.print();
}

class Person implements Printable {
  print(): void {
    console.log('Printing person...');
  }
}

let person: Person = new Person();
printValue(person);

在这个示例中,Printable是一个定义了print方法的接口。printValue函数是一个泛型函数,它接受一个必须扩展Printable的类型参数TPerson类实现了Printable接口。

结论

在本文中,你学习了TypeScript的基础知识。我们讨论了TypeScript的基本语法,比如变量、函数和类。你还学习了TypeScript的内置类型,比如数字、字符串和布尔值。我们讨论了TypeScript的内置枚举,比如数值枚举、字符串枚举和计算枚举。并且你学习了TypeScript的泛型类型,比如泛型函数和类。

感谢阅读!欢迎点赞收藏关注一键三连!!!


倔强青铜三
23 声望0 粉丝