一、背景
1、什么是TS?
TypeScript是⼀款开源的编程语⾔,通过在JavaScript的基础上添加静态类型定义构建⽽成。通过TypeScript编译器或Babel转译为JavaScript代码,可运⾏在任何浏览器,任何操作系统。
2、TS与JS的关系
TypeScript是JavaScript的⼀个超集,他们之间并不是所属关系,TypeScript扩展了JavaScript弱类型语⾔的限制,增加了更多的模块解析⽅式和语法糖。TypeScript并不是⼀个能独立运行的语⾔,⼤多数时候他都被转译成 JavaScript运⾏,所以可以简单的认为TypeScript相当于功能更丰富的编译型的JavaScript。
3、为什么要使用TS
JavaScript本身已完全可以满足完整的应用开发需求,但在⼤型项⽬协作开发或插件开发的场景中JavaScript弱类型语⾔的不足便暴露了出来。由于JavaScript并非编译型语⾔,在代码编写过程中⽆法轻松的实现良好的类型约束和类型推断。TypeScript强类型的约束性以及其⾯向接⼝编程的约束性可以让TypeScript语法开发的应⽤有极强的维护性,代价是更大量的代码篇幅。
4、TypeScript的性能优于JavaScript?
TypeScript通过编译后最终还是会转换为JavaScript,最终执行的还是JavaScript,并不能代表性能优势。然而TypeScript语⾔之所以流⾏,是因为其类型化的JavaScript在上下⽂阅读时可以提供更好的类型追溯,通过编辑器插件可以实现更友好的提示。
二、Hello Word
1、安装
npm install typescript -g // 全局安装
tsc -v // 查看版本号
参考文档:TypeScript 安装 | 菜鸟教程
2、Hello Word 实现
新建index.ts文件,代码如下:
let str:string = 'Hello Word';
console.log(str);
然后,终端执行以下命令将 TypeScript 转换为 JavaScript 代码:
tsc index.ts
此时在当前目录下(与 index.ts 同一目录)就会生成一个同名js文件 index.js
输入以下指令,完成终端编译js代码,打印输出 Hello Word 。
node index.js
三、基础类型
在JavaScript的基础上新增了几种数据类型,在定义值时要声明值的类型:let 变量名:变量类型 = 变量值。
注意:内置类型建议首字母小写,接口类型建议首字母大写。
1、JS内置类型
let num1:number = 1;
let str1:string = "1";
let unde1:undefined = undefined;
let null1:null = null;
let boo:boolean = true;
boo = 'false'; // 报错 不能将类型“string”分配给类型“Boolean”
联合类型
let val: number | string
val = 1
val = '哈哈哈'
非空断言
let not_null: string | null | undefined
console.log(not_null!.toUpperCase(), '非空断言') //编译通过
使用尖括号语法的类型断言
let str: any = "hello";
let len1: number = (<string>str).length;
使用as语法的类型断言
let str: any = "hello";
let len2: number = (str as string).length;
任何类型可以断言成any
any 可以断言成任何类型
2、any
不清楚用什么类型,可以使用 any 类型。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。
注:切勿滥用,不然就是去使用TS的意义了。
let num:any = 0;
num = '0'; 或 num = true;
// 上面定义了一个num类型为any,可以给他赋值为任意类型。any类型完全失去了TS的作用。
let a:any[]
a=[100,"关",true]
// 表示定义了一个名称叫做a的数组,这个数组中将来可以存储任意类型的数据
3、unknown
当不知道一个类型具体是什么时,该怎么办?可以使用 unknown 类型,它代表任何类型,它的定义和 any 定义很像,但是它是一个安全类型,使用 unknown 做任何事情都是不合法的。例如:
function getCount(num: unknown) {
return num / 2;
}
// 会提示错误:对象的类型为 "unknown"
// 解决方案
function getCount(num: unknown) {
return num as number / 2; // as为类型断言,将参数转换为number类型
}
4、void
void类型与 any 类型相反,它表示没有任何类型。比如函数没有明确返回值,默认返回 Void 类型。
function seeHello(): void {
console.log('hello')
}
// 定义了一个不可以保存任意类型数据的变量,只能保存null和undefined
let value:void;
// value = 100;//报借
// value =“杨";//报错
// value = true;//报错
// 注意点:null和undefined是所有类型的子类型,所以我们可以将null和undefined赋值给任意类型
// 严格模式下null会报错
// value= null; // 不会报错
value = undefined; // 不会报错
5、never
never类型表示的是那些永不存在的值的类型。
有些情况下值会永不存在,比如:
如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值,因为抛出异常会直接中断程序运行。
函数中执行无限循环的代码,使得程序永远无法运行到函数返回值那一步。
// 异常
function showError(msg: string): never {
throw new Error(msg)
}
// 死循环
function showLoop(): never {
while (true) {}
}
6、object
可以直接定义值为对象类型,也可以定义特定的接口对象来规范对象中每个值的类型(后面会提到)
const obj:object = {
name: '小明'
}
obj.age = 23; // 报错 类型“object”上不存在属性“age”
注意:声明对象时要规范数组每一项的类型,新添加一个不存在的值会导致语法错误。
// 解决方案
// Record是TS中内置工具类型,接收两个泛型参数
const obj1:Record<string, any> = {}
// 规范Obj1对象每一项键名必须是字符串,值的类型可以是any类型
7、Array数组类型
格式为:let 变量名:数组中类型[] = 变量值,注意:数组中的值一定要与定义的类型对应上要不然就会报错;
// 定义了一个数组每一项都是number类型的数组
let list: number[] = [1, 2, 3]
list.push(4)
list.push('5') // 报错 类型“string”的参数不能赋给类型“number”的参数。
如果数组想每一项放入不同数据怎么办?
元组类型:
ts中的元组类型其实就是数组类型的扩展;
元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let list: [number,string,boolean] = [1, '2', true];
list.push(null); // 报错 因为元祖类型中没有null类型定义
联合类型别名
type andType_1 = boolean[] | string[]
let andtypeVal_1: andType_1 = [true, 'false']
let myBeauty:(number|string|boolean)[];
myBeauty = ["刘亦菲","刘诗诗",18,true]
8、枚举
Enums(枚举)是TypeScript的少数功能之一,它不是JavaScript的类型级扩展。
枚举允许开发者定义一组命名的常量。使用枚举可以使其更容易记录意图或创建一组不同的情况。
TypeScript提供了基于数字和字符串的枚举。
(1)数值型枚举
enum Direction {
Up = 1,
Down,
Left,
Right,
}
上面,我们有一个数字枚举,其中 Up 被初始化为 1 ,所有下面的成员从这一点开始自动递增。换句话说,Direction.Up的值是 1 ,Down 是 2,Left是3,Right是4。
如果我们愿意,我们可以完全不使用初始化器:
enum Direction {
Up,
Down,
Left,
Right,
}
这里,Up的值是0,Down是1,依次类推。这种自动递增的行为对于我们可能不关心成员值本身,但关心每个值与同一枚举中的其他值不同的情况很有用。
使用枚举很简单:只需将任何成员作为枚举本身的一个属性来访问,并使用枚举的名称来声明类型:
enum UserResponse {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: UserResponse): void {
// ...
}
respond("Princess Caroline", UserResponse.Yes);
数字枚举可以混合在计算和常量成员中。简而言之,没有初始化器的枚举要么需要放在第一位,要么必须放在用数字常量或其他常量枚举成员初始化的数字枚举之后。换句话说,下面的情况是不允许的:
enum E {
A = getSomeValue(),
B,
// Ⓧ Enum成员必须有初始化器。
}
(2)字符串枚举
字符串枚举是一个类似的概念,但有一些细微的运行时差异,如下文所述。在一个字符串枚举中,每个成员都必须用一个字符串字头或另一个字符串枚举成员进行常量初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
(3)异构枚举
从技术上讲,枚举可以与字符串和数字成员混合,但不清楚为什么你会想这样做。除非你真的想以一种巧妙的方式利用JavaScript的运行时行为,否则建议你不要这样做。
enum demoEnum {
No = 0,
Yes = "YES",
}
枚举可以看成一个字典
注 1:使用关键字 【enum】 来定义枚举
注 2:枚举变量的首字母最好大写
注 3:枚举里面的元素的 key 最好大写
9、类型别名
类型别名就是给一个类型起个新名字, 但是它们都代表同一个类型
例如: 你的本名叫张三, 你的外号叫小三, 小三就是张三的别名, 张三和小三都表示同一个人
(1)给string类型起了一个别名叫做MyString, 那么将来无论是MyString还是string都表示string
type MyString = string;
let value:MyString;
value = 'abc';
value = 123;
value = false;
其他常见用法
type userId = string | number; // 联合类型
// 对象类型
type Person = {
id: userId; // 可以使用定义类型
name: userName;
age: number;
gender: string;
isWebDev: boolean;
};
const user: Person = {
id: "901",
name: "椿",
age: 22,
gender: "女",
isWebDev: false,
};
// 数组
type arr = number[];
const numbers: arr = [1, 8, 9];
(2)类型别名也可以使用泛型
type MyType<T> = {x:T, y:T};
let value:MyType<number>;
value = {x:123, y:456};//因为T是number,里面只能是number
value = {x:'123', y:456};//报错
value = {x:false, y:456};//报错
(3)在类型别名类型的属性中使用自己
type MyType = {
name:string,
// 一般用于定义一些树状结构或者嵌套结构
children?:MyType
}
let value:MyType = {
name:'one',
children:{
name:'one',
children:{
name:'one',
}
}
}
四、函数类型
TS 定义函数类型需要定义输入参数类型和输出类型。输出类型也可以忽略,因为 TS 能够根据返回语句自动推断出返回值类型。
function add(x:number, y:number):number {
return x + y
}
console.log(add(1,2)); // 3
// 函数表达式
let add2 = (x: number, y: number) => {
return x + y
}
console.log(add(1,4)); // 5
console.log(add(1,'5')); // 报错 类型“string”的参数不能赋给类型“number”的参数。
1、可选参数:
参数后加个问号,代表这个参数是可选的,放在函数入参的最后面
function add(x:number, y:number, z?:number):number {
return x + y
}
console.log(add(1,2,3),add(1,2)); // 3 3
2、默认参数:
跟 JS 的写法一样,在入参里定义初始值。与可选参数不同的是,默认参数可以不放在函数入参的最后面
function add(x:number = 100, y:number):number {
return x + y
}
console.log(add(1,4)); // 5
console.log(add(undefined,100)); // 200 当x为undefined会使用默认值
五、interface接口
interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
定义 interface 一般首字母大写。
注意:interface 不是 JS 中的关键字,所以 TS 编译成 JS 之后,这些 interface 是不会被转换过去的,都会被删除掉,interface 只是在 TS 中用来做静态检查
interface Person {
name: string
age: number
}
const son: Person = {
name: '张三',
sex: 1
}
// 报错:变量用了Person接口但是未定义sex属性;同时多写属性age但并未赋值使用也会报错
交叉类型
interface cross_1 {
name: string
age: number
}
interface cross_2 {
name: string
work: string
}
let cross_val: cross_1 & cross_2 = {
name: "王惊涛",
age: 28,
work: '前端开发者'
}
console.log(cross_val, '交叉类型值')
继承
interface NameItf {
name: string
}
interface AgeItf {
age: number
}
// 接口继承格式,特点:具有父接口的属性类型
interface PersonItf extends NameItf, AgeItf {
height: number
}
let p:PersonItf;
p = {
name: 'xiaotian',
age: 19,
height: 180
}
1、可选属性
跟函数的可选参数是类似的,在属性上加个 ?,这个属性就是可选的。
interface Person {
name: string
age?: number
}
const son: Person = {
name: '张三'
}
2、只读属性
希望某个属性不被改变
interface Person {
readonly id: number
name: string
age: number
}
const son: Person = {
id: 2,
name: '张三',
age: 20
}
son.id = 3; // 报错 无法分配到 "id" ,因为它是只读属性
3、描述函数类型
interface 也可以用来描述函数类型
interface ShowFn {
(x:number,y:number):number
}
const add:showFn = (num1, num2) => {
return num1 + num2
}
4、自定义属性(可索引的类型)
当一个对象上有多个不确定的属性时,就可以使用自定义属性。
interface ShowRandom {
[key: number]: number
}
const arr: ShowRandom = {
0: 0,
1: 1,
2: 2,
}
// 还可以这样写
const arr1:ShowRandom = [0,1,2];
// 看似像一个数组,其实是一个类数组,不能调用数组的属性和方法
5、duck typing(鸭子类型)
鸭子类型是很多面向对象(OOP)语言中的常见做法,interface 的写法非常灵活,用 interface 可以创造一系列自定义的类型。在 TypeScript 中,只要对象符合定义的类型约束,那么我们就可以视为他是,及成立。
interface ShowFn {
(num1:number ,num2:number):number;
name: string;
}
const fn:ShowFn = (num1,num2) => {
return num1 + num2
}
fn.name = "函数";
上面这个接口中函数类型添加了一大堆属性,完全四不像,但是却可完全正常地工作。这就是 duck typing做法的灵活之处。
六、接口interface & 类型别名type
1、接口和类型别名是相互兼容的
type MyType = {
name:string
}
interface MyInterface {
name:string
}
let value1:MyType = {name:'lnj'};
let value2:MyInterface = {name:'zs'};
value1 = value2;
value2 = value1;
2、interface 和 type 的相似之处
(1)两者都可以用来描述对象 Object 和函数 Function,但语法不同:
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
(2)二者都可以被继承
interface存在 extends 行为
type可以通过交叉类型实现 extends 行为
interface 继承 interface
interface Person{
name:string
}
interface Student extends Person { stuNo: number }
interface 继承 type
type Person{
name:string
}
interface Student extends Person { stuNo: number }
type 继承 type
type Person{
name:string
}
type Student = Person & { stuNo: number }
type 继承 interface
interface Person{
name:string
}
type Student = Person & { stuNo: number }
3、二者区别
(1)定义基本类型别名
type可以定义基本类型别名, 但是interface无法定义,如:
type userName = string
type stuNo = number
(2)声明联合类型
type可以声明联合类型, 例如:
type Student = {stuNo: number} | {classId: number}
(3)声明元组
type可以声明 元组类型:
type Data = [number, string];
以上都是 type能做到, 而interface做不到的, 接下来聊聊type做不到的
(4)声明合并
如果你多次声明一个同名的接口,interface 会将它们合并到一个声明中,并将它们视为一个接口。这称为声明合并, 例如:
interface Person { name: string }
interface Person { age: number }
let user: Person = {
name: "Tolu",
age: 0,
};
这种情况下,如果是type的话,重复使用Person是会报错的:
type Person { name: string };
// Error: 标识符“Person”重复。ts(2300)
type Person { age: number }