官网: https://www.typescriptlang.org/
官方文档: https://www.typescriptlang.org/docs/
- 参考:
- 视频 - 2022全新【TypeScript】 - https://www.bilibili.com/video/BV1N8411h7iZ/
文章目录
简介
TypeScript 是添加了类型系统的 JavaScript 的超集,可以编译成 JavaScript。
TypeScript 的应用非常广泛,最新的 Vue 和 React 均集成了 TypeScript。(这里推荐大家使用 Vue3,因为 Vue3 天然支持 TS)
另一方面,TypeScript 中有很多 ES 的语法:
💡 提示
其实 TypeScript 可以看成 JavaScript 的编译器(规则)。生搬硬套了 java 的一系列规则,然后跟原生的 js 做适配。
目的就是:
- 规范 js 的写法
- 检查代码(避免编码问题导致的运行时异常)
为大型开发、团队开发、更加深度的封装开发提供条件
然后老版就可以随便开人,或者不怕程序员跑路,而继任者看不懂代码了。
类型系统
从 TypeScript 的名字就可以看出来, 「类型」 是其最核心的特性。
弥补 JavaScript 没有类型概念,导致的类型混乱的现象。
let foo = 1;
foo.split(' ');
// js 运行时报错,导致线上 bug
// ts 编译时就会提示,无法通过编译
兼容:弱类型
ts 完全兼容 js,因此也是弱类型的
console.log(1+'1');
// 打印字符串 '11'
安装、编译
首先,需要下载 node.js
然后,命令行安装 ts,并验证
npm install -g typescript
$ tsc -v
Version 4.8.4
尝试编译一个 ts 文件
index.ts
let time: number = new Date().getDate();
let title: string = "hello world!";
console.log(`typescript: ${title} ${time}`);
编译命令
$ tsc index.ts
$ tsc index.ts --outFile build/index.js
输出 index.js
var time = new Date().getDate();
var title = "hello world!";
console.log("typescript: ".concat(title, " ").concat(time));
数据类型
# boolean
let flag: blloean = false;
# number
let num: number = 1;
# string
let name: string = 'Tom';
// 模板字符串
let sentence: string = `Hello, my name is ${name}`;
# void
javascript 中没有空值(void)的概念;在 TypeScript 中,可以用 void 标识没有任何返回值的函数
function alertName(): void {
alert('My name is Tom');
}
声明一个 void 类型的变量,一般没啥用,因为只能将它赋值为 underfined
或 null
let unsable: void = underfined;
# null、undefined
let u: undefined = undefined;
let n: null = null;
# any 任意值
如果是 any 类型,允许被赋值为任意类型
let item: any = 'seven';
item = 7;
在 any 上访问任何属性都是允许的
let tiem: any = 'hello';
console.log(item.name);
console.log(item.age);
item.setName('Tom');
⚠️ 需要注意的是, any 类型的处理也会被认为是 any 类型
let t_name: any = 'Tom';
let t_h = t_name.substring(0,1);
t_h = 1; // 编译通过
let t_name: string = 'Tom';
let t_h = t_name.substring(0,1);
t_h = 1; // error TS2322: Type 'number' is not assignable to type 'string'.
# xxxx[] 数组
let temp: number[] = [1,2,3,4]
let head: string[] = ["1", "2"]
函数(function)
在 TypeScript 中对函数的定义,需要把输入和输出考虑到:
函数声明 的写法
function sum(x: number, y: number): number {
return x + y;
}
函数表达式 的写法
let sum = function(x: number, y: number): number {
return x + y;
}
// 完整写法
let sum: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y;
}
# 可选参数
function show(firstName: string, lastName?: string): string {
return firstName + (lastName && " "+lastName || "")
}
console.log(`"${show("Tom", "Cat")}"`); // "Tom Cat"
console.log(`"${show("Boob")}"`); // "Boob"
# 参数默认值
在 ES6 中,允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数
function show(firstName: string, lastName: string = 'Cat-default'): string {
return firstName + (lastName && " "+lastName || "")
}
console.log(`"${show("Tom", "Cat")}"`); // "Tom Cat"
console.log(`"${show("Boob")}"`); // "Boob Cat-default"
# 剩余参数
ES6 中,可以使用 ...rest
的方式获取函数中的剩余参数(rest 参数):
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
})
}
let a = [];
push(a, 1,2,3);
console.log(a); // [ 1, 2, 3 ]
# 函数重载
重载允许一个函数接收不同数量或类型的参数时,作出不同的处理。
如:(不推荐,有更好的)
function reverse(x: number | string): string | void {
if(typeof x === 'number') {
return x.toString().split("").reverse().join("");
} else if(typeof x === 'string') {
return x.split("").reverse().join("");
}
}
然而,这样不能够精确的表达,也不便与 TypeScript 的处理。于是 TypeScript 提供了下面的写法:
function reverse(x: number): void;
function reverse(x: string): void;
function reverse(x: number | string): string | void {
if(typeof x === 'number') {
return x.toString().split("").reverse().join("");
} else if(typeof x === 'string') {
return x.split("").reverse().join("");
}
}
⚠️ 注意
TypeScript 会优先选择上面的函数作为匹配的定义。因此,精确的定义应该尽量放在上面。
类(class)
关于类的关键字,不少是 js 原生的
# constructor
# get、set
# static
# public
# private
关键字 | 支持 | 解释 |
---|---|---|
constructor | js | |
get、set | js(es5) | |
static | ts | |
public | ts | |
private | ts | 只是在 ts 层面做限制 |
e.g.
class Girl {
// js 原生写法
// public name: string;
// private age: number;
// constructor(name?: string, age?: number) {
// this.name = name || "";
// this.age = age || 0;
// }
// ts 简化写法
constructor(public name?: string, private age?: number) {
}
static hello() {
console.log("hello world!")
}
get b() {
return "B";
}
set b(d) {}
}
Girl.hello();
let alis = new Girl("alis", 16);
alis.b = "diik";
编译结果
var Girl = /** @class */ (function () {
// js 原生写法
// public name: string;
// private age: number;
// constructor(name?: string, age?: number) {
// this.name = name || "";
// this.age = age || 0;
// }
// ts 简化写法
function Girl(name, age) {
this.name = name;
this.age = age;
}
Girl.hello = function () {
console.log("hello world!");
};
Object.defineProperty(Girl.prototype, "b", {
get: function () {
return "B";
},
set: function (d) { },
enumerable: false,
configurable: true
});
return Girl;
}());
Girl.hello();
var alis = new Girl("alis", 16);
alis.b = "diik";
# abstract 抽象
抽象类不允许被实例化:
abstract class Animal {
public constructor(public name: string){}
public abstract eat(): void;
}
let a = new Animal("Jack");
// error TS2511: Cannot create an instance of an abstract class.
实例化抽象类
abstract class Animal {
public constructor(public name: string){}
public abstract eat(): void;
}
class Cat extends Animal {
public hello(): void {
console.log("miao~~~");
}
public eat(): void {
console.log(`${this.name} is eating.`);
}
}
let a = new Cat("Jack");
a.hello(); // miao~~~
a.eat(); // Jack is eating.
接口(Interfaces)
⭐️ 官方文档:https://www.typescriptlang.org/docs/handbook/interfaces.html
在 TypeScript 中,我们使用 Interface 来定义一个接口类型的对象。
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。接口的作用就是为这些类型命名和为实现代码定义契约。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
# 多余类型检查(Excess Property Checks)
实现如果多了属性是不允许的
let tom: Person = {
name: 'Tom',
age: 25,
price: 2,
// error TS2322: Type '{ name: string; age: number; price: number; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'price' does not exist in type 'Person'.
};
tom.title = '1'; // error TS2339: Property 'title' does not exist on type 'Person'.
实现如果少了属性也是不允许的
let tom: Person = { // error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
name: 'Tom',
};
# 可选属性(Optional Properties)
💡 如果希望实现的属性是可选的,可以如下写法:
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom',
};
# 只读属性(Readonly properties)
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
// Cannot assign to 'x' because it is a read-only property.
数组有 ReadonlyArray<T>
这个标识符
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
// Index signature in type 'readonly number[]' only permits reading.
ro.push(5); // error!
// Property 'push' does not exist on type 'readonly number[]'.
ro.length = 100; // error!
// Cannot assign to 'length' because it is a read-only property.
a = ro; // error!
//The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
只读数组类型(ReadonlyArray
)赋值给非只读类型时需要转换
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
let b: number[] = ro as number[];
# 索引类型(Indexable Types)(又称:任意类型)
可以指定某种类型的索引(签名),其值是什么类型。
默认的签名有两种类型: string
, number
string 签名
/*
[prop: string]: number 的意思是:
1. A 类型的对象可以有任意属性签名
2. string 指的是对象的键都是字符串类型的
3. number 则是指定了属性值的类型。
4. prop 类似于函数的形参,是可以取其他名字的。
*/
interface A {
[prop: string]: number;
}
const obj: A = {
a: 1,
b: 3,
};
number 签名
interface B {
[index: number]: string;
}
const arr: B = ['suukii'];
⚠️ 坑:其他类型的属性
像下面的写法是不成立的:
interface Person {
name: string;
age?: number;
[prop: string]: string;
}
// Property 'age' of type 'number' is not assignable to string index type 'string'.
需要改为:
interface Person {
name: string;
age?: number;
[prop: string]: string | number;
}
但这需要注意,正如一般的联合类型一样,它是会限制对象只能使用公共的属性和方法。换而言之,随便增加联合类型的种类,可能会导致原来正确的编译报错。
# 函数类型(Function Types)
如果要定义一个函数的接收值、返回值,可以用 type 定义,也可以用这里的 interface 定义:
interface 定义
interface createArray {
<T>(length: number, ...values: T[]): Array<T>;
}
let createArrayA: createArray = function<T>(length: number, ...values: T[]) {
let result: T[] = [];
for (let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
createArrayA<string>(3, 'x'); // ['x', undefined, undefined]
type 定义
type createArray = <T>(length: number, ...values: T[]) => Array<T>;
// interface createArray {
// <T>(length: number, ...values: T[]): Array<T>;
// }
let createArrayA: createArray = function<T>(length: number, ...values: T[]) {
let result: T[] = [];
for (let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
createArrayA<string>(3, 'x'); // ['x', undefined, undefined]
# 接口的实现(implements)
interface Alarm {
alert(): void;
}
abstract class Door {}
class SecurityDoor extends Door implements Alarm {
alert(): void {
console.log("SecurityDoor alert");
}
}
class Car implements Alarm {
alert() {
console.log("Car alert");
}
}
# 接口的合并
接口允许有重名
interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
// 相当于
interface Alarm {
price: number;
weight: number;
}
类型推论(Type Inference)
如果没有明确的指定类型,那么 TypeScript 会依据类型推论(Type Inference)的规则推断出一个类型。
-
如果定义时被赋值,其类型会继承值的类型
let t_name= 'Tom'; t_name = 7; // error TS2322: Type 'number' is not assignable to type 'string'.
-
如果定义时没有被赋值,其类型被推断为 any 类型
let t_name t_name = 'Tom'; t_name = 7;
类型断言(Type Assertion)
类型断言可以手动来指定一个值的类型
value as type
or
<type>value
⚠️ 注意
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 value as type 的写法。
故建议使用这种更加通用的写法。
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function handle(animal: Cat | Fish) {
if(typeof (animal as Cat).run == 'function') {
(animal as Cat).run();
} else if(typeof (animal as Fish).swim == 'function') {
(animal as Fish).swim();
}
return animal.name;
}
断言的时候能否对接口使用 instanceof
判断?
不行。
但是可以对接口(interface)的实现(implements)使用 instanceof
判断:
interface Animal{
name?: string;
age: number;
}
class Human implements Animal {
age = 0;
speak()
{
console.log("hello world!");
}
}
let A = new Human();
if(A instanceof Human) {
(A as Human).speak();
}
⚠️ 注意
类型断言 是 ts 的语法
类型转换 是 js 的语法
类型断言并不会导致类型转换。因此,当我们想转换类型的时候,还是要乖乖使用 js 的语法:
function toBoolean(something: any): boolean {
// return something as boolean; // 错误写法
return Boolean(something); // 正确写法
}
toBoolean(1);
💡 提示
类型声明比类型断言更加严格,因此尽量使用类型声明,而不是使用类型断言:
interface Animal {}
class Cat implements Animal {}
const animal1: Animal = new Cat(); // 类型声明 (👍🏻推荐)
const animal2 = new Cat() as Animal; // 类型断言
联合类型(Union Types)
联合类型(Union Types)标识取值可以为多种类型中的一种。(中间以 |
分割)
let t_name: string | number = 'Tom';
t_name = 1;
t_name = 'Jim';
⚠️ 当 TypeScript 不能确定一个联合类型的变量到底是哪一个类型的时候,我们只能访问此联合类型的共有属性或方法:
function getLength(something: string | number): number {
return something.length;
// error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
}
只有当联合类型被推断出确定的类型后,才能使用该类型特有的方法:
let a: string = "Tom";
let b: string | number = a;
console.log(b.length)
元组(Tuple)
let tom: [string, number] = ['Tom', 25]
tom[0] = 'jack';
tom[1] = 1;
tom[1] = 'jack'; // 报错,必须为 number 类型
当下标越界时,越界项目的类型必须是元组定义的类型的联合类型:
let tom: [string, number] = ['Tom', 25]
tom[0] = 'jack';
tom[1] = 1;
tom[2] = 'jack';
tom[3] = true; // 报错,必须为 number 或者 string 类型
泛型(Generics)
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。
function createArray<T>(length: number, ...values: T[]): Array<T> {
let result: T[] = [];
for(let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
let arr: Array<string> = createArray<string>(4, 'x', 'y', 'z');
console.log(arr); // [ 'x', 'y', 'z', undefined ]
// function getCacheData(key: string): any {
// return (window as any).cache[key];
// }
// interface Cat {
// name: string;
// run(): void;
// }
// const tom: Cat = getCacheData('tom');
// tom.run();
function getCacheData<T>(key: string): T {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData<Cat>('tom');
tom.run();
# 多个泛型
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
# 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
# 泛型接口
interface CreateArrayFunc {
<T>(length: number, ...values: T[]): Array<T>;
}
let createArray: CreateArrayFunc = function<T>(length: number, ...values: T[]) {
let result: T[] = [];
for (let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
createArray<string>(3, 'x'); // ['x', undefined, undefined]
或者把泛型 T 提前,这样在声明泛型引用的时候就要指定 T 的实际类型了:(推荐)
interface createArray<T> {
(length: number, ...values: T[]): Array<T>;
}
let createArrayA: createArray<string> = function<T>(length: number, ...values: T[]) {
let result: T[] = [];
for (let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
createArrayA(3, 'x'); // ['x', undefined, undefined]
# 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x,y) {
return x + y;
}
# 默认类型
在 TypeScript 2.3 以后,可以为泛型中的类型参数指定默认参数。 当使用泛型时,没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray<T=string>(length: number, ...values: T[]) {
let result: T[] = [];
for(let i=0; i<length; i++) {
result[i] = values[i];
}
return result;
}
关键字:type 定义类型别名
// function getName(n: string | (() => string)): string {
// if(typeof n === 'string') {
// return n;
// } else {
// return n();
// }
// }
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if(typeof n === 'string') {
return n;
} else {
return n();
}
}
关键字:type 定义字符串字面量
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
let ele: Element | null = document.getElementById("hello");
if(ele) {
handleEvent(ele, "click");
}
💡 提示
是的,类型别名和字符串字面量的定义都是使用 type 关键字
关键字:enum 枚举
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"]===0); // true
console.log(Days["Mon"]===1); // true
console.log(Days["Tue"]===2); // true
console.log(Days["Sat"]===6); // true
console.log(Days[0]==="Sun"); // true
console.log(Days[1]==="Mon"); // true
console.log(Days[2]==="Tue"); // true
console.log(Days[6]==="Sat"); // true
看编译后的 js 代码:
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
;
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
关键字:readonly 只读
class Animal {
readonly name;
public constructor(name: string) {
this.name = name;
}
}