MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript基础概念与开发入门指南

2022-02-022.8k 阅读

什么是 TypeScript

TypeScript 是一种由微软开发的开源、跨平台的编程语言,它是 JavaScript 的超集,这意味着任何合法的 JavaScript 代码都是合法的 TypeScript 代码。TypeScript 为 JavaScript 添加了可选的静态类型系统和基于类的面向对象编程。

为什么使用 TypeScript

  1. 代码可维护性:在大型项目中,随着代码量的增长,JavaScript 这种弱类型语言可能会导致很多难以调试的错误。而 TypeScript 的静态类型检查可以在编译阶段就发现许多类型不匹配的错误,大大提高了代码的可维护性。例如,在一个函数接收特定类型参数的情况下,如果使用 JavaScript,传入错误类型参数时只有在运行时才可能报错,而 TypeScript 在编译时就能指出问题。
// TypeScript 示例
function greet(name: string) {
    return `Hello, ${name}`;
}
// 传入数字会在编译时报错
greet(123); 
  1. 代码可读性:TypeScript 通过类型注解清晰地表明变量、函数参数和返回值的类型,使得代码的意图更加明确,其他人阅读代码时能更快理解。
let age: number = 30; 
// 从代码中可以清晰知道 age 是数字类型
  1. 智能代码补全:TypeScript 支持强大的代码编辑器(如 Visual Studio Code)提供智能代码补全功能,因为编辑器能根据类型信息预测可能的属性和方法,提高开发效率。

TypeScript 基础类型

基本类型

  1. 布尔类型(boolean):表示真或假。
let isDone: boolean = false; 
  1. 数字类型(number):在 TypeScript 中,所有数字都是浮点型,支持十进制、十六进制、八进制和二进制表示。
let myNumber: number = 42; 
let hexNumber: number = 0xf00d; 
let binaryNumber: number = 0b1010; 
  1. 字符串类型(string):用于表示文本数据,可以使用单引号或双引号定义。
let message: string = 'Hello, TypeScript'; 
let anotherMessage: string = "This is also a string"; 
  1. null 和 undefinednull 表示空值,undefined 表示未定义。在 TypeScript 的严格模式下,它们有自己的类型,并且只能赋值给自身和 void 类型。
let n: null = null; 
let u: undefined = undefined; 
  1. void 类型:通常用于表示函数没有返回值。
function logMessage(message: string): void {
    console.log(message); 
}
  1. never 类型:表示永远不会有返回值的函数的返回类型,或者表示那些根本不存在的值的类型。
function throwError(message: string): never {
    throw new Error(message); 
}

复杂类型

  1. 数组类型:可以使用两种方式定义数组类型。一种是在元素类型后面加上 [],另一种是使用泛型 Array<元素类型>
let numbers: number[] = [1, 2, 3]; 
let strings: Array<string> = ['a', 'b', 'c']; 
  1. 元组类型:元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let user: [string, number] = ['John', 30]; 
  1. 枚举类型:用于定义一组命名的常量。
enum Color {
    Red,
    Green,
    Blue
}
let myColor: Color = Color.Green; 
  1. 对象类型:用于描述具有特定属性和方法的对象。
let person: {
    name: string;
    age: number;
    greet: () => void;
} = {
    name: 'Alice',
    age: 25,
    greet: function() {
        console.log(`Hello, I'm ${this.name}`); 
    }
};
  1. 函数类型:可以通过类型注解来定义函数的参数和返回值类型。
let add: (a: number, b: number) => number = function(a, b) {
    return a + b; 
};
  1. 类型别名:使用 type 关键字可以给一个类型起一个新名字,方便复用。
type User = {
    name: string;
    age: number;
};
let user1: User = { name: 'Bob', age: 35 }; 
  1. 接口(Interface):与类型别名类似,用于定义对象的形状,但主要用于定义对象类型,并且在面向对象编程中有更丰富的特性。
interface Animal {
    name: string;
    age: number;
    speak(): string;
}
let dog: Animal = {
    name: 'Buddy',
    age: 5,
    speak() {
        return 'Woof!'; 
    }
};

类型推断

TypeScript 具有强大的类型推断能力,在很多情况下,编译器可以自动推断出变量的类型,而无需显式地进行类型注解。

基础类型推断

let num = 10; 
// TypeScript 自动推断 num 为 number 类型

函数返回值推断

function addNumbers(a, b) {
    return a + b; 
}
// 自动推断 addNumbers 的返回值为 number 类型

上下文类型推断

在某些情况下,类型可以从上下文环境中推断出来。例如,在函数调用时,参数的类型可以根据函数定义推断。

function printMessage(message: string) {
    console.log(message); 
}
let myMessage = 'Hello'; 
printMessage(myMessage); 
// myMessage 的类型从 printMessage 函数的参数类型推断为 string

类型注解

虽然 TypeScript 有类型推断,但在很多情况下,显式地使用类型注解可以让代码更清晰,避免潜在的错误。

变量类型注解

let count: number; 
count = 5; 

函数参数和返回值类型注解

function multiply(a: number, b: number): number {
    return a * b; 
}

联合类型与交叉类型

联合类型

联合类型表示一个值可以是多种类型之一。使用 | 分隔不同的类型。

let value: string | number; 
value = 'Hello'; 
value = 42; 

交叉类型

交叉类型表示一个值同时具有多种类型的特性。使用 & 连接不同的类型。

interface A {
    a: string;
}
interface B {
    b: number;
}
let ab: A & B = { a: 'test', b: 123 }; 

类型断言

类型断言用于告诉编译器一个值的类型,当你比编译器更清楚一个值的实际类型时可以使用。

“尖括号”语法

let someValue: any = 'this is a string'; 
let strLength: number = (<string>someValue).length; 

as 语法

let someValue: any = 'this is a string'; 
let strLength: number = (someValue as string).length; 

函数重载

函数重载允许在同一个作用域内定义多个同名函数,但参数列表或返回类型不同。

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    if (typeof a === 'number' && typeof b === 'number') {
        return a + b; 
    } else if (typeof a ==='string' && typeof b ==='string') {
        return a + b; 
    }
}
let result1 = add(1, 2); 
let result2 = add('Hello, ', 'world'); 

类与面向对象编程

类的定义

在 TypeScript 中,使用 class 关键字定义类。

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    speak() {
        return `My name is ${this.name}`; 
    }
}
let dog = new Animal('Buddy'); 
console.log(dog.speak()); 

继承

通过 extends 关键字实现类的继承。

class Dog extends Animal {
    breed: string;
    constructor(name: string, breed: string) {
        super(name);
        this.breed = breed;
    }
    speak() {
        return `I'm ${this.name}, a ${this.breed}`; 
    }
}
let myDog = new Dog('Max', 'Golden Retriever'); 
console.log(myDog.speak()); 

访问修饰符

  1. public:默认的访问修饰符,成员在类内外都可以访问。
  2. private:成员只能在类内部访问。
  3. protected:成员只能在类内部和子类中访问。
class Person {
    private age: number;
    protected name: string;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
class Employee extends Person {
    job: string;
    constructor(name: string, age: number, job: string) {
        super(name, age);
        this.job = job;
    }
    getDetails() {
        return `Name: ${this.name}, Age: ${this.age}, Job: ${this.job}`; 
    }
}
let emp = new Employee('John', 30, 'Engineer'); 
// console.log(emp.age);  // 报错,age 是 private
console.log(emp.getDetails()); 

泛型

泛型是 TypeScript 中一个强大的特性,它允许我们创建可复用的组件,这些组件可以支持多种类型,而不是特定类型。

泛型函数

function identity<T>(arg: T): T {
    return arg; 
}
let result = identity<number>(42); 
let strResult = identity<string>('Hello'); 

泛型类

class GenericBox<T> {
    private value: T;
    constructor(value: T) {
        this.value = value;
    }
    getValue(): T {
        return this.value;
    }
}
let numberBox = new GenericBox<number>(10); 
let stringBox = new GenericBox<string>('test'); 

泛型约束

有时候,我们希望对泛型类型进行一些限制,这就需要泛型约束。

interface Lengthwise {
    length: number;
}
function printLength<T extends Lengthwise>(arg: T) {
    console.log(arg.length); 
}
printLength('Hello'); 
printLength([1, 2, 3]); 
// printLength(10);  // 报错,number 类型没有 length 属性

模块

在 TypeScript 中,模块是一种将代码封装起来,并控制其作用域和访问权限的方式。

导出与导入

  1. 导出变量、函数和类
// utils.ts
export function add(a, b) {
    return a + b; 
}
export const PI = 3.14159; 
  1. 导入模块
import { add, PI } from './utils'; 
let result = add(2, 3); 
console.log(result); 
console.log(PI); 
  1. 默认导出
// greet.ts
export default function greet(name) {
    return `Hello, ${name}`; 
}
import greet from './greet'; 
let message = greet('John'); 
console.log(message); 

装饰器

装饰器是一种特殊类型的声明,它可以附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 的形式,expression 必须是一个返回函数的表达式。

类装饰器

function logClass(target) {
    console.log('Class decorated:', target.name); 
    return target;
}
@logClass
class MyClass {}

方法装饰器

function logMethod(target, propertyKey, descriptor) {
    console.log('Method decorated:', propertyKey); 
    return descriptor;
}
class MyService {
    @logMethod
    myMethod() {}
}

项目搭建与工具

初始化项目

使用 npm init -y 初始化一个新的 npm 项目。

安装 TypeScript

npm install typescript --save -dev

配置 TypeScript

生成 tsconfig.json 配置文件,可以使用 npx tsc --init 命令。该文件包含各种 TypeScript 编译选项,如 target(指定编译目标 ECMAScript 版本)、module(指定模块系统)等。

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "strict": true
    }
}

编译与运行

  1. 编译:运行 npx tsc 命令将 TypeScript 代码编译为 JavaScript 代码。
  2. 运行:使用 Node.js 等运行环境运行编译后的 JavaScript 代码。例如,如果是 Node.js 项目,可以使用 node main.js 运行。

通过以上对 TypeScript 的基础概念和开发入门的详细介绍,相信你已经对 TypeScript 有了一个全面的认识,可以开始在实际项目中应用它来提升代码质量和开发效率。在实际开发过程中,还需要不断学习和实践,以掌握更多高级特性和最佳实践。