TypeScript的学习
1.TypeScript简介
首先官网祭天 ---> https://www.tslang.cn/
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
我们使用一张图来说明TypeScript和JavaScript的关系:
使用更加简单的说法:
TypeScript = JavaScript + 类型约束 + 高级特性
二.环境准备
TypeScript最终会编译成js来运行,所以我们需要先准备好将TypeScript编译为js的环境。
a.安装TypeScript
首先全局安装ts
npm install -g typescript
然后可以使用命令来验证安装是否成功了。
tsc --version
b.使用tsc命令编译ts为js
tsc ./src/xxx.ts
然后我们就可以执行对应的js文件
c.配置tsconfig文件
但是每次都使用tsc命令编译单个文件会比较麻烦,所以我们可以做一个统一配置
先使用 tsc --init
命令初始化一个 tsconfig.json
文件
里面配置如下:
{ "compilerOptions": { "target": "ESNext", "module": "ESNext", "outDir": "./dist", "skipLibCheck": true, ... } }
然后我们只需要tsc一下,所有的ts文件就能被编译了
d.自动编译
之后我们只需要启动vscode的监听任务功能,就能自动编译了。
1.选择终端 -> 运行任务
2.选择typescript类型的任务
3.选择监视任务
三.变量类型约束
3.1.原始类型
string,number,boolean,symbol,null,undefined
export {}; // 第一行增加这个是为了使文件里的变量不污染全局 let num: number = 1; // number let str: string = "2"; // string let bool: boolean = true; // boolean let sy: symbol = Symbol(); // symbol let undef: undefined = undefined; // undefined let nul:null = null; // null let vd: void = undefined; // 可以把undefined类型赋值给void类型,但是反过来不行 // 函数没有返回值,那么函数的返回值类型就是void function fn(): void { return undefined; }
注意:
-
void只用在函数没有返回值的情形下。
-
undefined和null最大的价值主要体现在接口类型上,表示可缺省、未定义的属性;null表示对象或者属性是空值。这个可以先有个印象,后面说到接口会讲
-
单纯声明 undefined 或者 null 类型的变量是无比鸡肋的,上面的例子只是说明原始类型
-
3.2.非原始类型
小object, 大Object, {}
小object:代表的是非原始类型的类型,也就是不能是string,number,boolean,symbol,严格模式:多包括null,undefined
let obj1: object = 3; // 报错 let obj2: object = "3"; // 报错 let obj3: object = true; // 报错 let obj4: object = null; // 报错 let obj5: object = undefined; // 报错 let obj6: object = Symbol(); // 报错 let obj7:object = {a: 1, b: '2'}; let obj8:object = [1, 2, 3];
let obj1: Object = 3; let obj2: Object = "3"; let obj6: Object = Symbol(); let obj3: Object = true; let obj4: Object = null; // 报错 let obj5: Object = undefined; // 报错 let obj7: Object = { a: 1, b: "2" }; let obj8: Object = [1, 2, 3];
let arr1: Array<number> = [1, 2, 3]; arr1.push('3'); // 报错 arr1.push(5); let arr2: string[] = ['4', '5', 'a']; arr2[3] = '6';
3.4.字面量类型和联合类型和交叉类型
export {}; // 字面量类型 let num: 2 = 2; // num = 3; let str: "hello"; str = "hello"; // str = "123"; let bool: true; // bool = false;//报错 // 联合类型 a | b let num1: 2 | "3"; num1 = 2; num1 = "3"; // num1 = true;//报错 // 交叉类型 a & b let str1: string & number; let obj: { name: string } & { age: number }; obj = { name: "张三", age: 20, }; // 联合类型|要比交叉类型&优先级要低 let obj1: | ({ id: number } & { name: string }) | ({ id: string } & { age: number }); obj1 = { id: 1, name: "", }; obj1 = { id: "", age: 1, };
3.5.any类型和unknown类型
export {}; // any: 任意类型, 跳过类型检测 // unknown: 未知, Typescript3.0,描述类型不确定的变量,它会进行类型检测 let num: any = 1; num.toFixed(2); let num1: unknown; if (true) { num1 = 1; } // num.a=1; //报错 (num1 as {a:number}).a=3 // 类型缩小 if (typeof num1 === "number") { num1.toFixed(2); } // 类型缩小 (num1 as number).toFixed(2);//断言
3.6.never类型
never表示永远不会发生值的类型
function throwErrFn():never { throw new Error('出错了'); }
注意:
-
如果函数里是死循环,返回值类型也是never
-
四.接口
TypeScript 不仅能帮助前端改变思维方式,还能强化面向接口编程的思维和能力,而这正是得益于 Interface 接口类型。
4.1.定义变量和函数的类型
使用接口定义变量和函数参数的类型
interface PersonInfo { name: string; age: number; } // 定义变量的类型 let zhangsan: PersonInfo = { name: "张三", age: 20, }; // 定义数组的类型 interface ArrayNumber { [idx: number]: number; } let arr1: ArrayNumber = [1, 2, 3]; // 定义函数的类型 interface PersonFn { (p: PersonInfo): void; } let Person1: PersonFn = (obj)=> { console.log(obj.name, obj.age); };
注意:
-
很少使用接口类型来定义函数的类型,更多使用内联类型或类型别名配合箭头函数语法来定义函数类型;
4.2.继承
多个不同接口之间是可以实现继承的,但是如果继承的接口PersonInfo和被继承的接口NameInfo有相同的属性,并且类型不兼容,那么就会报错。
interface NameInfo { name: string; } interface AgeInfo { age: number; } interface PersonInfo extends NameInfo, AgeInfo { height: number; } let zs: PersonInfo = { name: "张三", age: 20, height: 177, };
多个不同的接口可以实现继承,组合成一个新的接口,那么如果出现多个相同名字的接口会怎么样?
多个相同名字的接口,会进行合并,得到一个新的接口;这个接口的特性一般用在扩展第三方库的接口类型。
interface PersonInfo { name: string, age: number } interface PersonInfo { name: string, height: number } let zs: PersonInfo = { name: "张三", age: 20, height: 177, };
interface PersonInfo { name?: string; // 缺省 readonly height: number; // 只读 }
五.类型别名
export {}; // type 类型名称 = 类型定义 type ArrType<V> = { [index: number]: V }; let arr: ArrType<number> = [1, 2]; type ObjType = { name?: string; age?: number }; let obj: ObjType = { name: "", age: 1, }; // 类型别名不能重名 // type ObjType // 类型别名是可以直接使用交叉类型和联合类型,接口类型不能 type Type1 = number | string | boolean; type Type2 = ArrType<number> & ObjType; // 定义函数 type FnType = (num: number, str: string) => 1; let fn: FnType = function (num, str) { return 1; }; // Required是Typescript自带的工具类型 type ReType = Required<ObjType>;
Interface 与 Type 的区别
实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
-
重复定义的接口类型,它的属性会叠加,这个特性使得我们可以极其方便地对全局变量、第三方库的类型做扩展
-
如果我们重复定义类型别名,那么就会报错
六.函数类型
6.1. 基础定义
显式指定函数参数和返回值的类型
const add = (a: number, b: number): number => { return a + b; }
或者用type来声明函数类型
type addFnType = (a: number, b:number) => number; let addFn: addFnType = (num1, num2) => { return num1 + num2; }
6.2. 函数参数类型
参数一般有:可选参数、默认参数、剩余参数;
1. 可选参数
在类型标注的:
前添加?
表示 log 函数的参数 x 就是可缺省的;
function log(msg?: string):void { console.log(msg); }
可缺省是不是相当于msg参数的类型就是和string | undefined
等价呢?这个当然不是,string | undefined
的意思是这两个类型中的一种,而可缺省是不传的意思。
function addFn1(num1: number = 1, num2: number = 2):number { return num1 + num2; }
function sum(...nums: number[]) { return nums.reduce((a, b) => a + b, 0); } sum(1, 2); // => 3 sum(1, 2, 3); // => 6
4. this
函数中的this问题,一直都是javascript最令人头疼的问题,因为this的指向只有函数调用的时候才能确定。还有一些可以改变this指向的方法(apply,call,bind)。
但是在Typescript中,必须要明确的指定this的类型(严格模式下)。
type objType = {person: (n: string) => void, myname: string}; function person(this: Window | objType , name: string):void { this.myname = name; console.log(this.myname); } window.person = person; window.person('window name'); let obj:objType = { person, myname: '' }; obj.person('obj name');
interface Window { person: (n: string) => void; myname: string; }
定义对象的函数属性时,只要实际调用中 this 的指向与指定的 this 指向不同,TypeScript 就能发现 this 指向的错误
interface ObjType2 { name: string; say: (this: ObjType2) => void; } let obj2:ObjType2 = { name: 'obj2', say() { console.log(this.name); } } obj2.say(); // ok let t11 = obj2.say; t11();
注意:
-
显式声明函数的返回值类型为 undfined,则会出现错误提示,如果没有返回值,我们用void表示;
-
注意:显式注解函数中的 this 类型,它表面上占据了第一个形参的位置,但并不意味着函数真的多了一个参数,因为 TypeScript 转译为 JavaScript 后,“伪形参” this 会被抹掉,这算是 TypeScript 为数不多的特有语法。
七.枚举
数字枚举和字符串枚举
枚举的作用在于定义被命名的常量集合,一个默认从0 开始递增的数字集合,称之为数字枚举.也可以指定值,这里可以指定的值 可以是数字或者字符串.
enum Days { Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } let day = Days.Sunday;
八.泛型
function getValue(val: string): string { return val; } function getValue1(val: number): number { return val; } function getValue2(val: unknown): unknown { return val; } let g1: string = getValue("1"); let g2: number = getValue1(1); let g3: unknown = getValue2(1); function getValue3<T>(val: T): T { return val; } let g4: number = getValue3<number>(3); let g5: string = getValue3<string>('4');
前面我们使用过Array<类型>来定义数组的类型,这里的Array也是一种类型。
在 TypeScript 中,类型本身就可以被定义为拥有不明确的类型参数的泛型,并且可以接收明确类型作为入参,从而衍生出更具体的类型。
// 定义数组类型 let arr: Array<number> = [1]; let arr1: Array<string> = [""]; // 类型别名 type typeFn<P> = (params: P) => P; let fntype: typeFn<number> = (n: number) => { return n; }; let fn1:typeFn<string> = (p: string):string => { return p; } // 定义接口类型 interface TypeItf<P> { name: P; getName: (p: P) => P; } let t1: TypeItf<number> = { name: 123, getName: (n: number) => { return n; }, }; let t2: TypeItf<string> = { name: "123", getName: (n: string) => { return n; }, };
把泛型入参限定在一个相对更明确的集合内,以便对入参进行约束。
interface TypeItf<P extends string | number> { name: P; getName: (p: P) => P; } let t1: TypeItf<number> = { name: 123, getName: (n: number) => { return n; }, }; let t2: TypeItf<string> = { name: "123", getName: (n: string) => { return n; }, };
九.class(类)
面向对象实践 OOP 编程思想,在实际工作中,都是极其有用的抽象、封装利器。
class Person { name: string; say(this: Person, song: string): Person { console.log(song); return this; } constructor(name: string) { this.name = name; } } let p1 = new Person('张三'); p1.say('Song').name;
使用extends关键字实现继承
class Male extends Person { age: number; constructor(name: string, age: number) { super(name); this.age = age; } }
在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。通过这三个修饰符做到控制属性和方法的访问。
-
public:基类、子类、类外部都可以访问;
-
protected:基类、子类可以访问,类外部不可以访问;
-
private:基类可以访问,子类、类外部不可以访问;
-
readonly:只读修饰符
class Person { public readonly name: string = '张三'; protected age: number = 20; private height: string = '180'; protected getPersonInfo():void { console.log(this.name, this.age, this.height); // 基类里面三个修饰符都可以访问 } } class Male extends Person { public getInfo():void { console.log(this.name, this.age); // 子类只能访问public、protected修饰符的 } } let m = new Male(); console.log(m.name); // 类外部只能访问public修饰的 m.name = '李四'; // name属性使用只读修饰符,所以不能对name进行赋值修改操作
3. 静态属性
class Person { static title: string = "个人信息"; } Person.title;
abstract class Person { abstract name: string; abstract getName(): void; extendsFn():void { console.log('扩展方法'); } } class Male extends Person { constructor(name: string) { super(); this.name = name; } name: string; getName(): void { console.log(this.name); } }
interface Person { name: string; age: number; getName: () => void; } class Male implements Person { constructor(name: string, age: number) { this.name = name; this.age = age; } name: string; age: number; getName(): void { console.log(this.name); } }
在声明类的时候,其实也同时声明了一个特殊的类型(确切地讲是一个接口类型),这个类型的名字就是类名,表示类实例的类型。
class Male { constructor(name: string, age: number) { this.name = name; this.age = age; } name: string; age: number; getName(this: Male): void { console.log(this.name); } } let m1: Male = new Male("张三", 20); let m2: Male = { name: "张三", age: 20, getName(this: Male) { console.log(this.name); }, }; m2.getName(); let fn = m2.getName; fn(); // 报错,this指向并不是Male对象
打造自己的工具类型,这个时候需要用到一些物料
extends关键字判断泛型参数P是否是string或者是number其中的一种,最终类型的确定由三元运算的结果决定。
type TypeFn<P> = P extends string | number ? P[] : P; let m: TypeFn<number> = [1, 2, 3]; let m1: TypeFn<string> = ['1', '2', '3']; let m2: TypeFn<boolean> = true;
类型推断infer相当于声明一个变量接收传入的类型
type ObjType<T> = T extends { name: infer N; age: infer A } ? [N, A] : [T]; let p: ObjType<{ name: string; age: number }> = ["张三", 1]; let p1: ObjType<{name: string}> = [{name: '张三'}];
Keyof提取对象属性名、索引名、索引签名的类型;
interface NumAndStr { name: string; age: number; [key: number]: string | number; } type TypeKey = keyof NumAndStr; // number | 'name' | 'age' let t:TypeKey = 'name';
4. in
type NumAndStr = number | string; type TargetType = { [key in NumAndStr]: string | number; }; let obj: TargetType = { 1: '123', "name": 123 }
注意:
-
-
in 和 keyof 也只能在类型别名定义中组合使用
5. typeof
typeof 的主要用途是在类型上下文中获取变量或者属性的类型
// 推断变量的类型 let strA = "2"; type KeyOfType = typeof strA; // string // 反推出对象的类型作为新的类型 let person = { name: '张三', getName(name: string):void { console.log(name); } } type Person = typeof person;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人