TypeScript 快速入门
关于JS的内容可以参考之前在hexo搭建的一个博客中的笔记(已经停止维护):
- JavaScript 学习笔记

参考教程:
- 小满TypeScript基础教程全集(完结)
- 20分钟学会TypeScript 无废话速成TS 学不会你来评论区

0 环境配置

安装node(MacOS)和TypeScript(TS)

brew install node

npm install -g typescript

在VSCode中安装插件JavaScript and TypeScript Nightly,提供基础的代码纠错和提示。

TS代码并不能直接在浏览器中执行,它需要先编译(转化)为JS代码才可以运行。可以使用tsc命令将TS代码编译成JS代码,运行后会在同目录下生成编译得到的同名JS文件:

tsc hello.ts

假设编辑了一个hello.ts文件,使用下面的命令即可运行代码(可以不执行转化直接运行,node会自动完成这一步骤):

node hello.ts   # 或 node hello.js

生成一个同目录下的TS配置文件tsconfig.json

tsc --init

1 基础变量

1.1 变量定义

TS 常见类型包括 numberstringbooleanarraytupleenum 、nullundefined等,此外也支持自定义类型。

在严格模式下,null类型的变量只能赋值nullundefined类型变量只能赋值undefined

TypeScript支持类型推断,但是大多数时候,为了体现TypeScript的高级特性,我们可以使用类型注解使得代码更“安全”:

let s1: string;     // changeable variable
const s2: string = "Hello, TypeScript!";    // constant variable

s1 = "Hello, World";
let s3 = `${s1}, TypeScript!`;  // template string, ES6 feature

console.log(s1);
console.log(s2);
console.log(s3);
TS中的字符串不需要一定使用双引号,使用单引号也可以。分号可写可不写。

TS 支持类型联合,限定某个变量的类型范围或者取值范围:

let v1: string | number;
v1 = "hello";
v1 = 42;

let color: "red" | "green" | "blue";
color = "red";
color = "green";
color = "blue";
// color = "yellow"; // Error: Type '"yellow"' is not assignable to type '"red" | "green" | "blue"'.

定义数组(TS中的数组只能是单一类型的集合):

let a1: number[] = [1, 2, 3];
let a2: Array<number> = [1, 2, 3];
let a3: (number | string)[] = [1, '2', 3];
let a4: Array<number | string> = [1, '2', 3];

let a5: readonly number[] = [1, 2, 3];
let a6: ReadonlyArray<number> = [1, 2, 3];
逆天的是,在TS中使用const声明的数组依然可以使用pop()push()等方法修改数组,数组和对象仅仅不能被重新赋值。如果希望整个数组都不能被修改,需要使用readonly关键字注明。(const的官方名称就是只读类型)
const a1 : number[] = [1, 2, 3];
const a2 : readonly number[] = [1, 2, 3];

a1.push(4);     // 合法,数组和对象仅仅不能被重新赋值
// a2.push(4);  // 非法,readonly数组不允许被修改

console.log(a1);

定义元组(元组定义了每个位置的元素的类型和整个元组的长度):

let a1: [number, string] = [1, '2'];
let a2: [number, string, ...boolean[]] = [1, '2', true, false];

let a3: readonly [number, string] = [1, '2'];
let a4: Readonly<[number, string]> = [1, '2'];

定义枚举:

enum Color {
    Red,
    Green,
    Blue
};

let c: Color = Color.Green;
let c2: string = Color[2];

console.log(c);  // Output: 1,Red和0对应,Green和1对应,Blue和2对应
console.log(c2); // Output: "Blue"
上面的代码在Node.js v24.9.0下可能需要命令node --experimental-transform-types xxx.ts才能执行。

1.2 运算

对于常见的加减乘除,TS和其他语言并没有什么区别,在这里就不作赘述了。逆天的是,TS对于一下的代码并不会报错,而是有自己的一套运算逻辑。

let result: string = "1" + "2";

console.log(result);    // "12": string
console.log(1 + '2');   // "12": string, wtf?
console.log(1 + 2);     // 3: number
这让我不得不重新审视TS作为强类型检查语言的优劣性。TS是JS的超集,同时也带来了JS的一些“毛病”。

1.3 任意类型 any

TS中的类型有层级结构,高层次的类型可以包含底层次的类型。层次结构如下所示:

  • 顶级类型 Top typeanyunknown
  • 对象 Object{}
  • 类型实例:Number String Boolean
  • 具体类型:number string boolean
  • 具体变量:1 "abc" true
  • never

如果将所有的类型都声明为any或者unknown,那么TS和JS就没什么差别了。

两个顶级类型的区别:unknown类型智能赋值给unknown类型或者any类型。并且unknown类型的对象不能访问任何的属性或者方法。所以unknownany更加安全。

1.4 Object

2 函数

定义函数时需要指明参数和返回值的类型。

function MyFunc (a: number, b: number): number {
    return a + b;
}

TS里面参数可以是可选的,也可以是有默认值的,甚至也可以接收多余的参数到一个数组中(注意必选参数定义时需要写在左侧):

function MyFunc(a: number, b: number = 10, c?: number, ...rest: number[]) : string {
    return `Hello, TypeScript! a: ${a}, b: ${b}, c: ${c}, rest: ${rest.join(", ")}`;
}

console.log(MyFunc(5));
console.log(MyFunc(5, 20));
console.log(MyFunc(5, 20, 30));
console.log(MyFunc(5, 20, 30, 40, 50, 60));
❯ node hello.ts
Hello, TypeScript! a: 5, b: 10, c: undefined, rest: 
Hello, TypeScript! a: 5, b: 20, c: undefined, rest: 
Hello, TypeScript! a: 5, b: 20, c: 30, rest: 
Hello, TypeScript! a: 5, b: 20, c: 30, rest: 40, 50, 60

TS支持使用function关键字定义匿名函数,将这个匿名函数赋值给一个变量以实现调用。

let func = function () {
    console.log("Hello, World!");
};

func();

TS也支持箭头函数(Lambda函数,或者可以理解为闭包),它是函数的一种简写形式,往往仅仅定义了输入参数和返回值的关系:

let result = (a: number, b: number): number => {
    return a + b;
};

let result2 = (a: number, b: number): number => a + b;  // 隐式 return

let str = () => "hello";    // JS 风格

console.log(result(5, 10));
console.log(result2(5, 10));
console.log(str());

3 接口、类和对象

3.1 接口 interface

interface和Rust中的structtrait类似,规定了一种类型中需要定义的变量或者方法。赋值的变量不能多也不能少。

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

const greet = (person: Person): string => {
    return `Hello, ${person.name}! You are ${person.age} years old.`;
};

const user: Person = { name: "Alice", age: 30 };
console.log(greet(user));

interface可以定义方法,初始化时需要提供方法的实现(这里就相当于rust中的trait了)

interface Person {
    name: string;
    age: number;
    sayHello(): void;   // 无参数,返回值是 void
}

const person: Person = {
    name: "Alice",
    age: 30,
    sayHello() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
};

person.sayHello();

interface还可以扩展和继承:

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

// 扩展 Person 接口,添加新的属性和方法
interface Person {
    sayHello(): void;
}

interface Employee extends Person {
    employeeId: number;
    department: string;
}

const employee: Employee = {
    name: "Alice",
    age: 30,
    employeeId: 12345,
    department: "Engineering",
    sayHello() {
        console.log(`Hello, my name is ${this.name}, I work in the ${this.department} department.`);
    }
};

employee.sayHello();  // 输出: Hello, my name is Alice, I work in the Engineering department.

有时候我们从后端接受的数据可以放在一整个interface中,但是我们只关心其中的某些变量,这时可以使用索引签名:

interface Person {
    name: string;
    age: number;
    [key: string]: any;     // Index signature 索引签名
}

// 我们只关心 name 和 age 属性
const person: Person = {
    name: "Alice",
    age: 30,
    occupation: "Engineer" // 可以添加额外的属性,可以是任何类型
};
很多时候,我们不希望在函数运行过程中修改方法的定义,或者修改某些属性,此时就可以将属性或者方法定义为只读的。
interface Person {
    name: string;
    age: number;
    readonly sayHello: () => void;      // readonly,只能用于箭头函数定义的方式
}

const p: Person = {
    name: 'Alice',
    age: 30,
    sayHello: () => {
        console.log('Hello');
    }
};

// p.sayHello = () => { console.log('Hi'); }; // Error: Cannot assign to 'sayHello' because it is a read-only property

p.sayHello(); // Output: Hello

有时候,我们可以使用接口规定特定的“函数类型”,使其具有特定的参数类似和返回值类型:

interface Fn {
    (a: string): number;        // Function type with a string parameter and number return type
}

const fn: Fn = (a) => {
    return a.length;
};

console.log(fn("hello"));       // Output: 5

3.2 类 class

3.2.1 类的基础用法

类是ES6才拥有的关键字。使用下面的方式创建一个类:类中可以有属性,也可以有方法。其中constructor是一个特殊的方法,它是构造函数。

class Person {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
    
    greet(): void {
        console.log(`Hello, my name is ${this.name}`);
    }
}

const person = new Person("Alice");
person.greet();

可以使用interface对类进行约束,使其必须拥有特定的属性和方法实现;也可以和interface一样进行继承,并且子类可以重写父类的方法:

interface Greetable {
    name: string;
    greet(): void;
}

class Person implements Greetable {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
    
    greet(): void {
        console.log(`Hello, my name is ${this.name}`);
    }
}

class Employee extends Person {
    jobTitle: string;

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

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

const person = new Person("Alice");
person.greet();         // Output: Hello, my name is Alice

const employee = new Employee("Bob", "Software Engineer");
employee.greet();       // Output: Hello, my name is Bob and I am a Software Engineer (调用了重写的方法)

当然,在上面的例子中,Empolyee类肯定也实现了Greetable接口,这由编译器自动推断。当然也可以手动标注:

class Employee extends Person implements Greetable {
    // ...
}

类可以使用privateprotectedpublic规定属性对外部的可见范围:

class Person {
    protected readonly name: string;    // 使用 protected 使得子类可以访问

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

    public greet(): void {
        console.log(`Hello, my name is ${this.name}`);
    }
}

class Employee extends Person {
    private readonly jobTitle: string;  // 使用 private 使得外部无法访问

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

    public greet(): void {
        console.log(`Hello, my name is ${this.name} and I am a ${this.jobTitle}`);
    }
}

const person = new Person("Alice");
person.greet();         // Output: Hello, my name is Alice

const employee = new Employee("Bob", "Software Engineer");
employee.greet();       // Output: Hello, my name is Bob and I am a Software Engineer (调用了重写的方法)
通过上面的一些例子,可以看到interfaceclass在某些功能上是相似甚至重复的。但是interface更加强调对数据的一种约束,它可以用于对对象{}的约束,也可以用于对类的约束。这一点功能的划分上我认为TS做的不好,所以能使用类的时候使用类最好,毕竟每种抽象方式都应该有其对应的用途。

3.2.2 setget

转载声明:

除特殊声明外,本站所有文章均由 debussy 原创,均采用 CC BY-NC-SA 4.0 协议,转载请注明出处:Include Everything 的博客
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇