简介
TypeScript 是 JavaScript 的超集,是 JavaScript(弱类型语言) 的强类型版本。
拥有类型机制
文件后缀 .ts
Typescript = type + ES6
TypeScript 和 JavaScript 的关系类似 less 和 css 的关系
TypeScript对 JavaScript 添加了一些扩展,如 class / interface / module 等,大大提升代码的可阅读性。
不能在浏览器直接执行,而是编译成 JavaScript (去掉类型和特有语法)后才会运行:
与 JavaScript 相比的优势
- 静态类型检查,可以在代码开发阶段就预知一些低级错误的发生。
类型声明文件
以为.d.ts 为后缀的文件,内容为 TS 语法编写的各种类型声明(定义数据,函数、接口或类的类型)
- 通常安装第三方库后,若缺少类型声明文件,会有报错提示安装必要的类型声明文件
- 若没有提示,则可以在 https://www.npmjs.com/ 中搜索相应的类型声明文件
搜索关键字为@type/第三方库的名称
学习资料
英文官网
https://www.typescriptlang.org/
中文文档
https://www.tslang.cn/docs/home.html
https://typescript.bootcss.com/
在线开发环境
https://www.typescriptlang.org/play/index.html
搭建本地开发环境
- 新建文件夹 TSdemo
- 初始化项目
npm init -y
- 安装必要的依赖
npm i -D typescript nodemon ts-node
- typescript 用于将 TS 编译成 JS
- nodemon 用于 node 进程的重启
- ts-node 用于直接执行 TS 类型的文件
- 项目目录下创建文件 index.ts
let myNname: string = "朝阳"; console.log(myNname);
- 修改 package.json 中的 scripts 为
"scripts": { "start": "nodemon --exec ts-node index.ts" },
- 初始化 ts 的配置
npx tsc --init
执行成功后,会生成文件 tsconfig.json
- 启动项目
声明类型
值类型
// 字符串 let myNname: string = "朝阳"; // 数字 let num: number = 10; // 布尔类型 let ifLogin: boolean = true; // 布尔类型支持赋值计算之后结果是布尔值的表达式 let bool: boolean = !!0 // null let n: null = null; // undefined let u: undefined = undefined; // symbol let s: symbol = Symbol();
数组 []
// 空数组 let arr: [] = []; // 元素只能是数字的数组(其他类型的写法类似) let arr1: number[] = [1, 2]; // 元素只能是数字或字符串的数组(| 表示或的关系) let arr2: (number | string)[] = [1, "朝阳"]; // 即是number类型也可能是string数组 const numbers1: number[] | string[] = ['123', '333'] // 正确 const numbers2: number[] | string[] = [123, '333'] // 错误 // 对象数组 let todoList: { id: number; label: string; done: boolean; }[] = [ { id: 1, label: "吃饭", done: false, }, { id: 2, label: "编程", done: false, }, ]; // 使用类型别名(type alias) type User = { name: string; age: number } // 存储对象类型的内容 const objectArr: User[] = [ { name: 'zws', age: 18 } ] // 构造函数声明类型 let arr_1: Array<number> = [1, 2]; let arr_2: Array<number | string> = [1, "朝阳"];
对象 {}
// 空对象 let o: {} = {}; let o2: object = {}; // 必选属性的对象(赋值时,声明的属性必须有!) let user: { name: string; age: number; } = { name: "朝阳", age: 35, }; // 可选属性的对象(可选属性需写在必选属性的后面!) let user2: { name: string; age?: number; } = { name: "晚霞", };
Object 、 {} 、 object 的区别
- 与Object类型相同的{}是最不具体的,可以将对象、数组和基元分配给它;
- object是更具体的,类似于{ [key: string]: any };可以给它分配对象和数组,但不能分配原始类型的数据;
- { [key: string]: string }是最具体的,它不允许任何原始类型、数组或具有非字符串值的对象被分配到它。
var o: object; o = { prop: 0 }; // OK o = []; // OK o = 42; // Error o = "string"; // Error o = false; // Error o = null; // Error o = undefined; // Error var p: {}; // or Object p = { prop: 0 }; // OK p = []; // OK p = 42; // OK p = "string"; // OK p = false; // OK p = null; // Error p = undefined; // Error var q: { [key: string]: any }; q = { prop: 0 }; // OK q = []; // OK q = 42; // Error q = "string"; // Error q = false; // Error q = null; // Error q = undefined; // Error var r: { [key: string]: string }; r = { prop: 'string' }; // OK r = { prop: 0 }; // Error r = []; // Error r = 42; // Error r = "string"; // Error r = false; // Error r = null; // Error r = undefined; // Error
类 class
class Person {} const me: Person = new Person() class Teacher { name: string age: number } const objectArr: Teacher[] = [ new Teacher(), { name: 'zws', age: 18 } ]
新增类型
以下类型为 TS 新增的,在 js 中不存在的数据类型
任意类型 any
当不确定变量的类型时(比如来自用户输入或第三方代码库的动态内容),可以使用,但尽量少用。
- 任何类型的值都可以赋值给 any 类型的变量
let a: any = '你好' a = 0 // 多数据类型的数组 let list: any[] = [1, true, "free"];
未知的类型 unknown
TypeScript3.0版本新增的类型
- 任何类型的值都可以赋值给 unknown 类型的变量
- any 与 unknown 的区别 : unknown 需要明确类型后执行操作,any 则不需要
let a: unknown = 1; let b = a + 1; // 会报错 “a”的类型为“未知”
可通过 as 声明类型解决
let a: unknown = 1; let b = (a as number) + 1;
永不存在的值 never
用于总会抛出异常或根本不会有返回值的函数表达式的返回值类型。
当变量被永不为真的类型保护所约束时,该变量也是 never 类型。
用途:
- 限制类型
- 控制流程
- 类型运算
// 返回never的函数必须存在无法达到的终点 function error(message: string): never { throw new Error(message); } // 推断的返回值类型为never function fail() { return error("Something failed"); } // 返回never的函数必须存在无法达到的终点 function infiniteLoop(): never { while (true) { } }
无类型 Void
即什么类型都不是,通常用于描述函数没有返回值
function test(): void { }
void
类型的变量只能被赋予undefined
或null
let test: void = undefined;
元组 Tuple
即固定长度和类型的数组
let x: [string, number]; x = ['hello', 10];
枚举 enum
一种全新的数据描述方式,用于描述一组数量有限的系列数据。
数值枚举(默认)
// 枚举三原色 enum Color {Red, Green, Blue} // 按枚举内容的字符串取值,得到的是对应的下标(类似数组的下标,从0开始) let c: Color = Color.Green; // c的值为1
// 枚举方向:上下左右 enum Direction { Up, Down, Left, Right } // 按下标取值,可得到枚举内容的字符串 console.log(Direction[0]) // "Up"
可以自定义下标的起点
// 将默认的下标 0 改为 下标 1,则后续下标会依次递增 enum Direction { Up = 1, Down, // 2 Left, // 3 Right // 4 }
也可以自定义任意下标,未定义的在上一个的基础上递增
enum Direction { Up = 11, Down, // 12 Left = 6, Right, // 7 }
甚至下标可以相同
enum Direction { Up = 11, Down, // 12 Left = 11, Right, // 12 } console.log(Direction.Down); // 打印 12 console.log(Direction); // 打印 { '11': 'Left', '12': 'Right', Up: 11, Down: 12, Left: 11, Right: 12 }
如果下标使用了计算值或常量,那么该字段后面紧接着的字段必须设置初始值,不能默认递增值了!
const getValue = () => { return 0; }; enum ErrorIndex { a = getValue(), b, // error 枚举成员必须具有初始化的值 c } enum RightIndex { a = getValue(), b = 1, c } const Start = 1; enum Index { a = Start, b, // error 枚举成员必须具有初始化的值 c }
字符串枚举
枚举成员为字符串时,其之后的成员也必须是字符串。
enum Direction { Up, // 未赋值,默认为0 Down = '南', Left = '西', Right = '东' }
类型断言 as
当你知道更确切的类型时,可以使用类型断言,类似类型转换,但不进行特殊的数据检查和解构。
它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设程序员已经进行了必须的检查。
let someValue: any = "this is a string";
方式一:as 【推荐】
let strLength: number = (someValue as string).length;
JSX中,只可用 as
语法断言
方式二:<>
let strLength: number = (<string>someValue).length;
函数
声明函数类型
- 如果省略参数的类型,TypeScript 会默认这个参数是 any 类型;
- 如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;
- 如果函数有返回值,那么 TypeScript 会根据我们定义的逻辑推断出返回类型。
- 函数体内使用的外部变量的类型,不会体现在函数类型定义中。
// 命名函数 function add(arg1: number, arg2: number): number { return x + y; } // 箭头函数 const add = (arg1: number, arg2: number): number => { return x + y; };
// 定义变量 add 并声明为函数类型 let add: (x: number, y: number) => number; add = (arg1: number, arg2: number): number => arg1 + arg2;
使用 Interface 声明函数类型
interface Add { (x: number, y: number): number; }
使用 type 声明函数类型
type Add = (x: number, y: number) => number;
可选参数
可选参数需放置在必选参数之后,用 ? 标注
let add: Add = (arg1: number, arg2?: number): string => arg1 + arg2;
默认参数
- = 号标注参数的默认值
- 所有必须参数后面的带默认初始化的参数都是可选的
function add(x: number, y: number = 20): number { return x + y }
剩余参数
const handleData = (arg1: number, ...args: number[]) => { // };
函数重载
强类型语言中的函数重载:定义几个函数名相同,但参数个数或类型不同的函数,在调用时传入不同的参数,编译器会自动调用适合的函数。
TS 中的函数重载:通过为一个函数指定多个函数类型定义,从而对函数调用的返回值进行检查。
- 只能用 function 来定义,不能使用接口、类型别名等。
// 这个是重载的一部分,指定当参数类型为string时,返回值为string类型的元素构成的数组 function handleData(x: string): string[]; // 这个也是重载的一部分,指定当参数类型为number时,返回值类型为string function handleData(x: number): string; // 这个就是重载的内容了,这是实体函数,不算做重载的部分 function handleData(x: any): any { if (typeof x === "string") { return x.split(""); } else { return x .toString() .split("") .join("_"); } } handleData("abc").join("_"); handleData(123).join("_"); // error 类型"string"上不存在属性"join" handleData(false); // error 类型"boolean"的参数不能赋给类型"number"的参数。
接口 interface
用于自定义任意类型
interface Point { x: number; y: number; } interface Point { x: number, y: number }
- 每个属性间的间隔,可以说
;
也可以是,
interface Person { name: string, age: number } // 使用范例 let user: Person = { name: '朝阳', age: 30 }
可选属性 ?
interface Point { x: number; y?: number; }
可选属性的位置没有限制,无需像函数的可选参数一样,必须放在必传参数的后面。
只读属性 readonly
interface Point { readonly x: number; readonly y: number; }
赋值后, x
和y
再也不能被改变
let p1: Point = { x: 10, y: 20 }; p1.x = 5; // error!
属性的合并
interface Person { name: string; } interface Person { age: number; } let user: Person = { name: "朝阳", age: 35, };
但已经指定过类型的属性,不能改为新类型
interface Person { name: string; } interface Person { name: number; // 报错 后续属性声明必须属于同一类型 }
若想定义为联合类型,也需在最初的地方定义!
interface Person { name: string | number; }
定义函数类型
interface SumFunc { (a: number, b: number): number; } let c: SumFunc = (a, b) => a + b; let d = c(1, 2);
定义索引类型
当定义了索引类型之后,数组的length方法,将不存在,包括Array原型链上的其他方法也不存
interface Dic { [id: number]: string; } const dic1: Dic = { 0: "你", 1: "好", }; const dic2: Dic = ["你", "好"];
定义类类型(含接口的实现 implements )
用于给类添加约束,比如限定类必须含有某类型的属性/方法等。
// 定义接口 ClockInterface,有一个属性 currentTime,值类型为 Date interface ClockInterface { currentTime: Date; } // 定义类 Clock 实现接口 ClockInterface class Clock implements ClockInterface { // 因接口 ClockInterface 中有一个Date 类型的属性 currentTime,所以 类 Clock 也必须有这个属性 currentTime: Date; constructor(arg: Date) { // 在构造方法中,需给接口 ClockInterface 限定的属性 currentTime 赋值 this.currentTime = arg; } }
可简写为
class Clock implements ClockInterface { constructor(public currentTime: Date) {} }
绕开多余属性的类型检查
方式一:索引签名 【推荐】
interface myType { name: string; // 添加索引签名来兼容多余属性 [prop: string]: any; }
方式二:类型断言 【不推荐】
强行声明其为目标类型
let myInfo: myType = { name: "朝阳", age: 30, } as myType;
方式三:类型兼容 【不推荐】
interface myType { name: string; } // 使用解构赋值,避开多余属性 let getName = ({ name }: myType) => { return name; }; let myInfo = { name: "朝阳", age: 30, }; console.log(getName(myInfo));
接口的继承 extends
与类的继承类似
// 基础类型 Person interface Person { name: string; } // Student 类型在 Person类型的基础上,新增了学号 sid interface Student extends Person { sid: number; } // Teacher 类型在 Person类型的基础上,新增了学科 class interface Teacher extends Person { class: string; } let student1: Student = { name: "朝阳", sid: 1, }; let teacher1: Teacher = { name: "朝阳", class: "前端开发", };
同时继承多个接口
extends 后写多个接口即可,用 ,
间隔
interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; }
接口继承类
- 接口继承了类之后,会继承成员(类型),但是不包括实现;
- 接口还会继承 private 和 protected 修饰的成员,但是这个接口只可被这个类或它的子类实现
// 定义类 Person class Person { name: string; constructor(arg: string) { this.name = arg; } } // 接口 I 继承类 Person interface I extends Person {} // 接口 I 只能被 类 Person 或 类 Person 的子类实现 implements class Student extends Person implements I {}
泛型
在定义函数、接口或类的时候不预先指定数据类型,而在使用时再指定类型的特性。
泛型的作用
泛型可以提升应用的可重用性,如使用其创建组件,则可以使组件可以支持多种数据类型。
使用泛型
let printNum = (arg: number) => { console.log(arg); }; printNum(123); printNum("123"); // 会报错,因参数只能是数字
let printString = (arg: string) => { console.log(arg); }; printString(123); // 会报错,因参数只能是字符串 printString("123");
怎样才能写一个通用的打印方法呢?
使用泛型!
let printAnyType = <T>(arg: T) => { console.log(arg); }; printAnyType(123); printAnyType("123");
可见泛型即将类型设定为一个变量,当代码执行时传入的类型是啥,它就是啥,从而大大拓展了代码的通用性。
泛型的语法
- 用
<>
包裹 - 类型的变量通常用大写字母 T 表示,也可以是任意其他大写字母
多个泛型
// 定义函数 - 让元组中的两个元素互换位置 let exchange = <T, U>(tuple: [T, U]): [U, T] => { return [tuple[1], tuple[0]]; }; let tuple1: [number, string] = [1, "朝阳"]; let tuple2 = exchange(tuple1); console.log(tuple2);