TypeScript 学习笔记

TypeScript 学习笔记

一、TypeScript 简介

TypeScript 是一种由微软开发和维护的开源编程语言,它在JavaScript的基础上增加了静态类型定义。

  • TypeScript 为 JavaScript 添加了类型语法,可以在编辑器中尽早捕捉类型错误。
  • TypeScript 代码会转换为 JavaScript,可以在任何运行 JavaScript 的地方运行。
  • TypeScript 能够理解 JavaScript,并且无需额外的代码,便能提供类型推断。
// JavaScript:无明确类型
let num = 42
// TypeScript:有明确类型
let num: number = 42

二、TypeScript 基础

1. TypeScript 安装、运行

安装
  • 下载 Node.js 并安装(包含npm)
    终端输入node -v npm -v,验证安装是否成功
  • 使用 npm 安装 TypeScript,然后终端输入tsc -v验证
npm install -g typescript
编译运行
  • 创建 ts 文件
// hello.ts
console.log("Hello, world!");
  • 在终端使用 tsc 生成一个与 ts 文件同名的 js文件,再使用 node 执行 js
tsc hello.ts
node hello.js
  • 简化过程,使用 ts-node 直接执行 ts
npm i -g ts-node
ts-node hello.ts

2. 基本语法

变量声明
var x = 10; 	// 全局或函数作用域
let y = 20; 	// 块级作用域
const z = 30; 	// 常量,块级作用域
原始类型
let isDone: boolean = false;		// 布尔型

let decimal: number = 6;			// 数字型,支持十进制、十六进制、二进制和八进制
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

let color: string = "blue";			// 字符串,可选单双引号,支持模板字符串和嵌入表达式。
color = 'red';
let fullName: string = `Alex Cheng`;
let age: number = 21;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;

let u: undefined = undefined;		// 空值

let n: null = null;					// 未定义

let sym1 = Symbol();				// Symbol唯一标识符
let sym2 = Symbol("key"); // 可选描述
console.log(sym1); // Symbol()
console.log(sym2); // Symbol(key)
console.log(Symbol("key") === Symbol("key")); // false,Symbol唯一
其他基本类型
let list: number[] = [1, 2, 3];		// 数组,两种方式
let list: Array<number> = [1, 2, 3];

let tuple: [string, number];		// 元组
tuple = ["hello", 10];
// tuple = [10, "hello"]; 错误

enum Color { Red, Green, Blue }		// 枚举,默认以0开始编号
//enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

let notSure: any = 42;				// 任意类型
notSure = "maybe a string";
notSure = false;

function warnUser(): void {
  console.log("This is my warning message");
}									// 空类型,通常可用于函数返回值

function error(message: string): never {
    throw new Error(message);
}									// 永不存在的值的类型
类型推断
let message = "Hello, world"; // 推断为string类型
// message = 42; // 错误: number类型不能分配给string类型
类型断言

类型断言是告诉编译器某个值的类型,不让编译器自己推断。
语法:<Type>valuevalue as Type
示例:在Web开发中,处理DOM元素时经常需要使用类型断言,因为从DOM中获取的元素默认类型是HTMLElement,而不是更具体的类型,如HTMLInputElementHTMLButtonElement等。

// 假设 <input id="username" type="text">
let inutElement = document.getElementById('username');
let inputElementAsInput = inputElement as HTMLInputElement;
console.log(inputElementAsInput.value);

三、TypeScript 中的函数

1. 函数的基本定义

函数声明

function add(x: number, y: number): number {
    return x + y;
}

函数表达式

const add = function(x: number, y: number): number {
    return x + y;
};

箭头函数

const add = (x: number, y: number): number => x + y;

2. 函数的参数

可选参数

function greet(name: string, greeting?: string): string {
    if (greeting) {
        return `${greeting}, ${name}`;
    } else {
        return `Hello, ${name}`;
    }
}

默认参数

function greet(name: string, greeting: string = 'Hello'): string {
    return `${greeting}, ${name}`;
}

剩余参数

function buildName(firstName: string, ...restOfName: string[]): string {
    return `${firstName} ${restOfName.join(" ")}`;
}
let NameToBuild = buildName("Alex", "X", "X", "Cheng"); // "Alex X X Cheng"

3. 函数的重载

function double(value: number): number;
function double(value: string): string;
function double(value: any): any {
    if (typeof value === 'number') {
        return value * 2;
    } else if (typeof value === 'string') {
        return value + value;
    }
}

4. 泛型函数

function identity<T>(value: T): T {
    return value;
}
let numberIdentity = identity<number>(42); 		// 返回 42
let stringIdentity = identity<string>('hello'); // 返回 'hello'

四、接口和类型别名

1. 接口定义和使用

基本接口

interface Person {
    name: string;
    age: number;
}
let user: Person = {
    name: "Alex Cheng",
    age: 20
};

可选属性

interface Person {
    name: string;
    age?: number;
}
let user: Person = {
    name: "Alex Cheng"
};

只读属性

interface Point {
    readonly x: number;
    readonly y: number;
}
let point: Point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.

2. 继承接口

基本基础

interface Shape {
    shape: string;
}
interface Square extends Shape {
    sideLength: number;
}
let square: Square = { shape: "square", sideLength: 42 };

多重继承

interface Shape {
    shape: string;
}
interface Color {
    color: number;
}
interface Square extends Shape, Color {
    sideLength: number;
}
let square: Square = { shape: "square", color: 1, sideLength: 10 };

3. 函数接口

interface SearchFunc {
    (source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(source: string, subString: string): boolean {
    return source.indexOf(subString) > -1;
};
console.log(mySearch("Hello,world", "world")); // true

4. 泛型接口

interface KeyValuePair<K, V> {
    key: K;
    value: V;
}
const pair1: KeyValuePair<string, number> = { key: "age", value: 20 };
const pair2: KeyValuePair<number, string> = { key: 1, value: "one" };
console.log(pair1); // 输出: { key: "age", value: 20 }
console.log(pair2); // 输出: { key: 1, value: "one" }

5. 基本类型别名

type Name = string;
let myName: Name = "Alex";

6. 联合类型和交叉类型

// 联合类型
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "Hello";	// OK
value = 123;     	// OK

//交叉类型
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
type Point = PartialPointX & PartialPointY;
let point: Point = { x: 10, y: 20 };

7. 何时使用接口/类型别名

需要定义一个新的对象类型或函数类型时,优先使用接口。
需要使用联合类型、交叉类型或元组时,使用类型别名。

五、类和继承

1. 类的定义

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {		//构造函数
        this.name = name;
        this.age = age;
    }
    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}
const person = new Person('Alex', 20);
person.greet(); // Hello, my name is Alex and I am 20 years old.

2. 继承和派生类

class Student extends Person {
    studentId: number;
    constructor(name: string, age: number, studentId: number) {
        super(name, age);		//继承父类的name、age
        this.studentId = studentId;
    }
    displayStudent() {
        console.log(`My student ID is ${this.studentId}.`);
    }
}
// My student ID is 123456.

3. 实现接口

interface Animal {
  name: string;
  makeSound(): void;
}
class Dog implements Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  makeSound() {
    console.log("Woof!");
  }
}
const myDog = new Dog("Ben");
myDog.makeSound(); // Woof!

4. 方法重写

class ExchangeStudent extends Student {
    constructor(name: string, age: number, studentId: number) {
        super(name, age, studentId);
    }
    greet() {
        console.log(`Hello, I am exchange student ${this.name}.`);
    }
}
const exchangeStudent = new ExchangeStudent('Alex', 20, 12345);
exchangeStudent.greet(); // Hello, I am exchange student Alex.
exchangeStudent.displayStudent(); // My student ID is 12345.

5. 修饰符

  • public:默认修饰符,属性和方法可以在任何地方访问。
  • private:属性和方法只能在类内部访问。
  • protected:属性和方法可以在类内部及其子类中访问。
class Animal {
    public name: string;
    private age: number;
    protected species: string;
    constructor(name: string, age: number, species: string) {
        this.name = name;
        this.age = age;
        this.species = species;
    }
    protected displayInfo() {
        console.log(`Name: ${this.name}, Age: ${this.age}, Species: ${this.species}`);
    }
}
class Dog extends Animal {
    constructor(name: string, age: number, species: string) {
        super(name, age, species);
    }
    public getSpecies() {
    	this.displayInfo();
        console.log(`Species: ${this.species}`); // 访问受保护的属性
    }
}
const dog = new Dog('Ben', 5, 'X');
//dog.displayInfo();// Property 'displayInfo' is protected and only accessible within class 'Animal' and its subclasses.
dog.getSpecies(); 	// Name: Ben, Age: 5, Species: X
					// Species: X

6. 静态成员

静态成员属于类而不是实例,可以通过类名直接访问。

class MathUtils {
    static Pi: number = 3.14;
    static calculateCircumference(radius: number): number {
        return 2 * MathUtils.Pi * radius;
    }
}
console.log(MathUtils.Pi); // 3.14
console.log(MathUtils.calculateCircumference(10).toFixed(1)); // 62.8

7. 抽象类

抽象类不能被实例化,只能被继承,用于定义子类的通用结构。

abstract class Vehicle {
    abstract move(): void;
    start() {
        console.log('Vehicle started.');
    }
}
class Car extends Vehicle {
    move() {
        console.log('Car is moving.');
    }
}
const car = new Car();
car.start(); // 输出: Vehicle started.
car.move(); // 输出: Car is moving.

8. 泛型类

class Pair<K, V> {
    constructor(public key: K, public value: V) {}
    display(): void {
        console.log(`Key: ${this.key}, Value: ${this.value}`);
    }
}
let pair = new Pair("age", 30);
pair.display(); // 输出: Key: age, Value: 30

六、模块和命名空间

1. 模块的导入和导出

// 默认导出(MyClass.ts)
export default class MyClass { ... }
// 导入默认导出(main.ts)
import MyClass from './MyClass';

// 命名导出(myModule.ts)
export const myVar = 123;
export function myFunction() { ... }
export class MyClass { ... }
//或者
const myVar = 123;
function myFunction() { ... }
class MyClass { ... }
export { myVar, myFunction, MyClass }
// 导入命名导出(main.ts)
import { myVar, myFunction, MyClass } from './myModule';
// 重命名导入(main.ts)
import { myVar as newVarName } from './myModule';

2. 命名空间

// 定义
namespace MyNamespace {
  export class MyClass { ... }
  export function myFunction() { ... }
}
// 使用
let myClassInstance = new MyNamespace.MyClass();
MyNamespace.myFunction();
//定义嵌套命名空间
namespace OuterNamespace {
  export namespace InnerNamespace {
    export class MyClass { ... }
  }
}
//使用嵌套命名空间
let innerClass = new OuterNamespace.InnerNamespace.MyClass();

七、高级类型

1. 字面量

替代枚举类型
限制函数参数和返回值的取值范围

  • 字符串字面量类型
type Direction = 'left' | 'right' | 'up' | 'down';
function move(direction: Direction) { }
  • 数字字面量类型
type Port = 80 | 443 | 8080;
function startServer(port: Port) { }
  • 布尔字面量类型
type YesOrNo = true | false;
function isAdminUser(userType: YesOrNo) { }

2. 类型保护

typeof类型保护
是通过检查变量的类型来确定其类型的一种方式。它通常用于基本数据类型(如 stringnumberbooleansymbolundefinedobject)的检查。

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${typeof padding}'.`);
}

instanceof类型保护
用来检查一个对象是否是特定类的实例。它在面向对象编程中非常有用。

class Bird {
    fly() {
        console.log("The bird is flying.");
    }
}
class Fish {
    swim() {
        console.log("The fish is swimming.");
    }
}
function move(animal: Bird | Fish) {
    if (animal instanceof Bird) {
        animal.fly();
    } else if (animal instanceof Fish) {
        animal.swim();
    } else {
        throw new Error("Unsupported animal type");
    }
}

in 类型保护
用来检查对象是否具有某个属性名(或者键名)。

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
type Shape = Square | Rectangle;
function area(shape: Shape): number {
    if ("size" in shape) {
        return shape.size * shape.size;
    }
    if ("width" in shape && "height" in shape) {
        return shape.width * shape.height;
    }
    throw new Error("Unsupported shape");
}

3. 可辨识联合

// 定义可辨识联合类型
interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
// 联合类型
type Shape = Square | Rectangle | Circle;
// 函数示例,根据不同类型计算面积
function calculateArea(shape: Shape): number {
    switch (shape.kind) {
        case "square": return shape.size * shape.size;
        case "rectangle": return shape.width * shape.height;
        case "circle": return Math.PI * shape.radius ** 2;
        default: return assertNever(shape); // 防止遗漏
    }
}
// 辅助函数,确保所有类型都被处理
function assertNever(value: never): never {
    throw new Error(`Unhandled value: ${value}`);
}
// 使用示例
const square: Square = { kind: "square", size: 5 };
console.log(calculateArea(square)); // 25

4. 索引类型

索引签名
使用索引签名允许对象中任意属性的类型声明。
示例:{ [key: string]: any; }

// 支持字符串和数字作为索引类型
// 数字型索引(模拟数组)
interface myArray<T> {
    [index: number]: T;
}
let arr: myArray<number> = [1, 2, 3];

// 字符型索引
interface myArray1<T> {
    [index: string]: T;
}
let arr1: myArray1<number> = { A: 1, B: 2, C: 3 }

索引类型查询操作符(keyof)
keyof 操作符用于获取类型的所有属性名。
示例:type Keys = keyof { foo: number, bar: string }; // "foo" | "bar"

索引访问操作符
[] 操作符允许我们根据类型的索引类型来访问其属性类型。
示例:Person['name'] 的类型为 string

interface Person {
    name: string;
    age: number;
    location: string;
}
type NameType = Person['name'];
const person: Person = {
    name: "Alice",
    age: 30,
    location: "New York"
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}
const name: string = getProperty(person, "name");
const age: number = getProperty(person, "age");
const location: string = getProperty(person, "location");
console.log(name, age, location); // Alice 30 New York

5. 实用类型

(1) Partial<T>
  • 定义:将类型T的所有属性变为可选属性
  • 语法:type Partial<T> = { [P in keyof T]?: T[P] };
  • 示例:
interface User {
  id: number;
  name: string;
  email: string;
}
const updateUser: Partial<User> = {
  name: "John Doe"
};
(2) Required<T>
  • 定义:将类型T的所有属性变为必选属性
  • 语法:type Required<T> = { [P in keyof T]-?: T[P] };
  • 示例:
interface User {
  id?: number;
  name?: string;
  email?: string;
}
const user: Required<User> = {
  id: 1,
  name: "John Doe",
  email: "john.doe@example.com"
};
(3) Readonly<T>
  • 定义:将类型T的所有属性变为只读属性
  • 语法:type Readonly<T> = { readonly [P in keyof T]: T[P] };
  • 示例:
interface User {
  id: number;
  name: string;
  email: string;
}
const user: Readonly<User> = {
  id: 1,
  name: "John Doe",
  email: "john.doe@example.com"
};
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
(4) Record<K, T>
  • 定义:构造一个对象类型,其属性键为类型K,属性值为类型T
  • 语法:type Record<K extends keyof any, T> = { [P in K]: T };
  • 示例:
type Role = "admin" | "user" | "guest";
interface UserInfo {
  name: string;
  email: string;
}
const users: Record<Role, UserInfo> = {
  admin: { name: "Admin", email: "admin@example.com" },
  user: { name: "User", email: "user@example.com" },
  guest: { name: "Guest", email: "guest@example.com" }
};
(5) Pick<T, K>
  • 定义:从类型T中选择一组属性K,构造一个新的类型
  • 语法:type Pick<T, K extends keyof T> = { [P in K]: T[P] };
  • 示例:
interface User {
  id: number;
  name: string;
  email: string;
}
type UserPreview = Pick<User, "id" | "name">;
const user: UserPreview = {
  id: 1,
  name: "John Doe"
};
(6) Omit<T, K>
  • 定义:从类型T中排除所有属于类型U的属性
  • 语法:type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  • 示例:
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}
type UserWithoutEmailAndAge = Omit<User, 'email' | 'age'>;
const user: UserWithoutEmailAndAge = {
  id: 1,
  name: 'John Doe',
  // email: 'john.doe@example.com' // 属性不存在
  // age: 30 // 属性不存在
};
(7) Exclude<T, U>
  • 定义:从类型T中排除所有属于类型U的属性
  • 语法:type Exclude<T, U> = T extends U ? never : T;
  • 示例:
type T = "a" | "b" | "c";
type U = "a" | "c";
type Excluded = Exclude<T, U>; // "b"
(8) Extract<T, U>
  • 定义:从类型T中提取所有属于类型U的属性
  • 语法:type Extract<T, U> = T extends U ? T : never;
  • 示例:
type T = "a" | "b" | "c";
type U = "a" | "c";
type Extracted = Extract<T, U>; // "a" | "c"
(9) Extract<T, U>
  • 定义:从类型T中排除nullundefined
  • 语法:type NonNullable<T> = T extends null | undefined ? never : T;
  • 示例:
type T = string | number | null | undefined;
type NonNullableT = NonNullable<T>; // string | number
(10) ReturnType<T>
  • 定义:获取函数类型T的返回类型

  • 语法:type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

  • 示例:

function getUser() {
  return { id: 1, name: "John Doe" };
}
type User = ReturnType<typeof getUser>; // { id: number, name: string }
(11) InstanceType<T>
  • 定义:获取构造函数类型T的实例类型

  • 语法:type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

  • 示例:

class User {
  constructor(public id: number, public name: string) {}
}
type UserInstance = InstanceType<typeof User>; // User

八、类型兼容性

1. 接口的兼容性

接口的兼容性是基于对象的形状进行的。如果一个对象包含了目标接口所需的所有属性,那么它们是兼容的。

interface Point {
    x: number;
    y: number;
}
let point: Point;
let point3D = { x: 1, y: 2, z: 3 };
point = point3D; // 兼容,point3D包含point所需的所有属性
// point3D = point; // 不兼容,point没有z属性

2. 类的兼容性

如果类包含私有或受保护的成员,那么只有源类和目标类是同一个类时,它们才互相兼容。

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}
class Size {
    feet: number;
    constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
a = s; // 兼容,形状一致
s = a; // 兼容,形状一致

3. 函数的兼容性

目标函数的参数必须是源函数参数的超集,目标函数返回值类型必须是源函数的子集。

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // 兼容,因为y可以忽略s参数
// x = y; // 不兼容,因为x缺少s参数

let fn1 = () => ({ name: 'Alice' });
let fn2 = () => ({ name: 'Alice', location: 'Seattle' });
fn1 = fn2; // 兼容,fn2的返回值包含fn1的所有属性
// fn2 = fn1; // 不兼容,fn1返回值缺少location

4. 枚举不兼容

不同枚举类型之间是不兼容的,即使它们的值相同。

5. 泛型的兼容性

泛型类型在兼容性判断时会考虑实际的类型参数。

interface Empty<T> {
    value: T;
}
let x1: Empty<number>;
let x2: Empty<string>;
// x1 = x2; // 不兼容,参数类型不同

let identity = <T>(x: T): T => x;
let reverse = <U>(y: U): U => y;
identity = reverse; // 兼容,结构相同

九、类型声明文件

类型声明文件.d.ts,用于声明 JavaScript 代码中的变量、函数、类等的类型。让 TypeScript 编译器理解这些 JavaScript 代码中的结构和接口,以提供类型检查和自动补全功能。

declare const myVariable: string;
declare function myFunction(arg: number): boolean;
declare class MyClass {
  constructor(name: string);
  greet(): string;
}
declare interface MyInterface {
  property: string;
  method(arg: number): void;
}

// 模块声明
declare module 'my-module' {
  export function myFunction(arg: number): string;
}
// import { myFunction } from "my-module";

对于流行的第三方 JavaScript 库,通常会有现成的类型声明文件。这些声明文件可以通过 DefinitelyTyped 项目获取,并通过 npm 安装。例如:npm install @types/lodash

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值