前言
大家好,本文是我在学习TypeScript过程中的一些总结。
目前版本仅写到关于typeScript基础使用,模块、命名空间、修饰符等内容,在总结时总感觉案例有些苍白。我会在后面ts的项目demo中进行总结和输出。
我是通过观看教学视频和ts官方文档的结合进行学习。在学习过程中晦涩难懂的部分,我也会查看一些大佬发出来的经验贴进行吸收。
发出这篇文章的本意是希望能够加深自己的印象,同时能够将自己学习的内容进行一定的输出。
当然我作为一个菜鸡总结的还不是很到位。如果各位大佬发现了问题希望能直言不讳指出来,我会及时改正,谢谢支持。ღ( ´・ᴗ・` )比心
Part 01 - TypeScript
TypeScript
是微软开发的一门编程语言,是JavaScript
的一个超集。TypeScript
扩展了的语法,所以任何现有的JavaScript程序可以运行在TypeScript
环境中。TypeScript
是为大型应用的开发而设计,并且可以编译为JavaScript。TypeScript
的学习成本并没有想象中的大。作为渐进式的编程语法,你完全可以前期直接编写JavaScript
的代码,之后在通过学习了解一个特性新增一个特性。
Part 02 - 安装TypeScript及基础命令
安装TypeScript
npm i typescrpit --dev
执行ts文件编译为js文件
tsc .\getingTs.ts
执行配置文件初始化
tsc -- init
中文报错提示
tsc -- locale zh-CN
Part 03 - TypeScript配置文件
tsconfig.json
如果一个目录下存在一个tsconfig.json
文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json
文件中指定了用来编译这个项目的根文件和编译选项
目录结构
---- src/
---- dist/
|---- index.js // 编译后js文件
|---- index.ts // 整个工具库的入口
|---- tsconfig.json//配置文件
常用配置项 – 因为太多了因此只列出常用的
详细可查看官方文档
https://www.tslang.cn/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"target": "ES5", // 目标语言的版本
"lib": [ "es5","es2015"], // 标准库声明
"module": "commonjs", // 指定生成代码的模板标准
"noImplicitAny": true, // 不允许隐式的 any 类型
"removeComments": true, // 删除注释
"preserveConstEnums": true, // 保留 const 和 enum 声明
"sourceMap": true // 源代码映射,生成目标文件的sourceMap文件
"rootDir": "src", // 入口文件夹
"rootDirs": ["src","src/class",], // 设置多个入口文件夹
"outDir": "dist", // 编译后输出文件夹
"strictNullChecks": true, // 检查变量是否不能为null
"experimentalDecorators": true // 支持装饰器特性
},
"files": [ // 指定待编译文件
"./src/index.ts"
]
}
Part 04 - TypeScript基础数据类型
TypeScript
支持与JavaScript
几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
Boolean布尔值类型
const doBoolean: boolean = false; // true
Number数值类型
ts中规定的Number类型除了常数
外,NaN
,Infinity
,浮点数
都是支持的,包含十进制十六进制二进制和八进制的字面量。
const doNumber: number = 1;
const doNaN: number = NaN;
const doInfinity: number = Infinity;
const doHex: number = 0xf00d
Sring字符串类型
除了可以识别正常的字符串
外,模板字符串
和拼接字符串
也都是可以正常使用的,也可以结合其他变量进行定义。
const doString: string = `foo`;
const testString: string = `foo${doString}and${doNumber + 1}`;
const spliceString: string = 'foo'+doString+'and'+doNumber + 1;
Object对象类型
object
并非表示普通的对象类型,而是泛指所有非原始类型。
也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。
也就意味着当定义为object
类型,所有的原始类型都不被指定。
Error: const foo: object = 123; //不能将类型“number”分配给类型“object”。
同时object
类型也不单指普通的对象
const foo1: object = function name() {};
const foo2: object = [];
const foo3: object = {};
当然你也可以通过字面量形式去定义对象
注意如果添加了指定的成员,必须要保证一一对应上,既不能多也不能少
const foo4: { a: number; b: string } = { a: 1, b: "1" };
Array数组类型
有两种方法可以定义数组
方法一:可以在元素类型后面接上 []
,表示由此类型元素组成的一个数组
const arr1: number[] = [1, 2];
const arr2: object[] = [{}, {}];
const arr3: any[] = [{}, function () {}];
方法二:可以数组泛型
来进行表示
const arr1: Array<string> = ["a", "b"];
const arr2: Array<object> = [{}, {}];
const arr3: Array<any> = [{}, function () {}];
tuple元组类型
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
const tuple: [number, string] = [18, "a"];
但是数组的内的类型和数量必须与指定类型一致,若不一致则会报错。
const tuple: [number, string] = ["Ethen",18];// Error
const tuple: [number, string] = [18, "Ethen",'boy'];// Error
enum枚举类型
默认情况下,从0
开始为元素编号给成员进行赋值
enum 枚举名称 {成员1,成员2,...}
enum Family {Mom,Father,Me} // Mom=0 Father=1 Me=2
enum Color { Red, Green,Blue,}
并且枚举数值只作为可读属性在定义后不能进行更改。
而枚举是一种类型,可以作为变量的类型注解
let c: Color = Color.Green; // c=1
当然你也可以手动的指定成员的数值。但是注意如果初始化赋值为字符串类型
,其他成员也要进行相应赋值。
enum num {
one = 1,
two , //2
three , //3
}
Family {
Mom = 'emily',
Father='joe' ,
Me='mark' ,
}
并且我们可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到哪个名字,我们可以查找相应的名字。
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; // Green
Any任意类型
any
代表任意类型,也就意味任何类型都可以被指定。同时作为动态类型
和js的类型使用是没有任何区别的,也就代表着仍然存在着类型隐患。
因此any
类型一般多用于不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。
例如 :document.getElement
JSON.stringfy
等
const flag: any = 'string'; //number undefined ....
Void无效类型
void
它表示没有任何类型,通常用于一个函数没有返回值进行指定。
通常没有任何意义,因为只能赋值为null
和undefined
(如果设置不能为null则只能赋值undefined)
function void(): void {
console.log("no return");
}
const noTarget: void = undefined;
Never类型
never
类型如字面意义表示的是那些永不存在的值的类型。
//当你在函数内部抛出一个异常时,此函数永远不会有返回值,因此定义为Never类型
function error(message: string): never {
throw new Error(message);
}
Part 05 - TypeScript类型特性
TypeScript
除了使用以上类型以外还拥有很多类型的特性。
类型推断
在TypeScript
里,在有些没有明确指出类型的地方,类型推断会协助你指定变量的类型。
let age = 18; // Number
age = 'string' //Error
当然如果你在声明变量时没有指定任何类型,那他会默认为Any
类型,那么后续进行任何赋值操作也都是被认可的
let age // Any
age = 'string' //true
age = 18 //true
那么还有一种情况,当你在使用引用数据类型时,如:在数组内部使用了多个表达类型。TypeScript
会根据这些表达式的类型来推断出一个最合适的通用类型
let age=[18,'string',null] // (string | number | null)[]
类型断言
当我们在使用TypeScript
时你会遇到这么一种情况,你会比程序更了解这个变量的值的类型。
例如:
const tel = [110, 120, 119];
const emergency = tel.find((i) => i ==110 || i==120); // number | undefined
然而在TypeScript
推断时会认为undefined
的情况,而我们能够确定不会出现,这种情况下通过类型断言这种方式可以告诉编译器没有特殊情况发生。
那通常我们会使用两种方法来定义类型断言
第一种: 通过(xxx as type)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
第二种: 通过(<type>xxx)
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
这里需要注意如果是jsx文件,那么尖括号的书写方式可能会有语法冲突,因此推荐使用as的推断方式
Part 06 - TypeScript类Classes
classes类可以用来描述一类具体事物的抽象特征
类的定义
这里类的使用和js中的使用方法没有什么不同,包含同样的成员,即属性,构造函数和方法。
但值得注意的是在ts中你必须在声明时在属性或构造函数中设定初始值,而不是像es6中在构造函数动态添加
class student {
name: string;
age: num;
constructor(name: string, age: num) {
this.name = "张三";
this.age = 18;
}
study(course: string): void {
console.log(`${this.name}今年${this.age}岁了。他今天准备上${course}课`);
}
}
类的继承
TypeScript
中的类同样可以通过子类继承父类后子类的实例,拥有父类中的属性和方法。同样通过super去调用父类的实例和获取父类的this等等。并且仍然可以通过 extends
关键字来实现继承。这种增强代码的可复用性在TypeScript
中被保留了下来。
class student {
public name: string;
public age: num;
constructor(name: string, age: num) {
this.name = "张三";
this.age = 18;
}
study(course: string): void {
console.log(`${this.name}今年${this.age}岁了。他今天准备上${course}课`);
}
}
class four extends student {
constructor(name: string, age: num) {
super(name, age);
this.name=name
this.age=age
}
study(grade: string): void {
console.log(`${this.name}今年${grade}年级`);
super.study("语文");
}
}
const Li = new four("李四", 22);
Li.study("三");
//李四今年三年级
//李四今年22岁了。他今天准备上语文课
类的修饰符
private 私有属性 只能在当前类的内部去访问
class student {
private goodStudent: string = "三好学生";
}
class four extends student {
constructor() {
super();
this.goodStudent // Error 属性“goodStudent”为私有属性,只能在类“student”中访问。
}
}
const Li = new four("李四", 22);
Li.study("三");
Li.goodStudent // Error 属性“goodStudent”为私有属性,只能在类“student”中访问。
但当我们把private
修饰符交给constructor
去使用,则既不能被继承也不能在外部进行实例化,只能够通过类内部的静态方法去实现。
class student {
private constructor(name: string, age: num) {
}
static go() {
return new student();
}
}
class four extends student { // 无法扩展类“student”。类构造函数标记为私有。
}
const Li = new student(); // 无法扩展类“student”。类构造函数标记为私有。
const create = Li.go() //Success
protected # 受保护属性 只能在类的内部去访问(可以作用在继承子类中)
class student {
protected goodStudent: string = "三好学生";
}
class four extends student {
constructor() {
super();
this.goodStudent // Success
}
}
const Li = new four("李四", 22);
Li.study("三");
Li.goodStudent // Error 属性“goodStudent”为私有属性,只能在类“student”中访问。
而protected
作用在构造函数中类似private
的使用,但是是可以被继承的
readonly 只读属性 只读属性必须在声明时或构造函数里被初始化
class Student {
readonly name: string;
readonly age: number = 18;
constructor(studentName: string) {
this.name = studentName;
}
}
class Four extends Student{
constructor(name:string){
super(name)
console.log(this.name);
}
}
let three = new Student("张三");
let four = new Four("李四");
three.name = "王五"; // 错误! 无法分配到 "name" ,因为它是只读属性。
four.name = "赵六"; // 错误! 无法分配到 "name" ,因为它是只读属性。
类的存取器
TypeScript
支持通过getters
和setters
来截取对对象成员的访问。 它能帮助你控制对对象成员的访问。
例如以下的这个方法,我们在修改权限之前通过set方法去进行判断密码的正确与否,如果不能通过控制权限则fullname仍然能够通过get取到旧的姓名。
let passcode = "密码";
class Employee {
private _fullName: string = "旧名字";
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "密码") {
this._fullName = newName;
} else {
console.log("权限更新失败");
}
}
}
let employee = new Employee();
employee.fullName = "新名字";
if (employee.fullName) {
console.log(employee.fullName);
}
抽象类
和接口的使用方法类似,同样是对成员的抽象,但不同于接口的是抽象类可以包含成员的实现细节。
通过abstract
关键字来声明和于定义抽象类和在抽象类内部定义抽象方法。
但是抽像类并不能被创建为实例。只能通过派生的子类继承后使用,并且super方法也不能访问父类中的抽象方法。
abstract class Family {
people(name: string, relation: string): void {
console.log(`我${relation}叫${name}`);
}
abstract eat(food: string): void;
}
class Father extends Family {
eat(food: string): void {
console.log(food);
}
}
const father = new Father();
const family = new Family(); // Error!无法创建抽象类的实例。
father.eat("苹果");
Part 07 - TypeScript接口
你可以把接口理解为一种规范,可以用来规范对象的结构和类型。
在TypeScript
里,接口的作用就是约定对象当中包含了哪些成员,而这些成员的类型是如何指定的,以此来制定规范。
接口Interface
我们通过interface
关键字来约定对象成员的结构,也就是说你必须拥有接口所约束的所有成员,并且成员必须符合指定的类型。
interface rsvrConfig {
resName: string;
resCode: number;
resType: Array<string>;
}
function createRsvr(rsvrData: rsvrConfig) {
console.log(rsvrData.resName);
console.log(rsvrData.resCode);
console.log(rsvrData.resType);
}
const rsvrData = {
resName: "怀柔水库",
resCode: 2002011,
resType: ["PP", "ZZ", "ZQ"],
};
createRsvr(rsvrData);
可选属性
可选属性相当于在定义interface
的时候,有些属性是可有可无的并不影响使用的,那么我们就会在定义时加一个?
来表示。
interface rsvrConfig {
resName?: string;
resCode: number;
}
function createRsvr(rsvrData: rsvrConfig) {
console.log(rsvrData.resCode);
}
const rsvrData = {
resCode: 2002011,
};
createRsvr(rsvrData);
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
如:在属性名拼写错误时的提示
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly
来指定只读属性
interface rsvrConfig {
readonly resName: string;
}
function createRsvr(rsvrData: rsvrConfig) {
rsvrData.resName = '密云水库' // !!! Error 只读属性
console.log(rsvrData.resName);
}
const rsvrData = {
resName: '怀柔水库',
};
createRsvr(rsvrData);
同时TypeScript具有ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
interface rsvrConfig {
resType: ReadonlyArray<string>;
}
function createRsvr(rsvrData: rsvrConfig) {
rsvrData.resType[0] = "密云水库"; // !!! Error 只读属性
console.log(rsvrData.resType);
}
const rsvrData = {
resType: ["怀柔水库"],
};
createRsvr(rsvrData);
函数类型
接口除了描述带有属性的普通对象外,接口也可以描述函数类型。它相当于给函数定义了参数的类型。
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string):boolean {
let result = source.search(subString);
return result > -1;
};
动态类型
当你不知道你的接口是否需要新增其余的未知字段,可以通过[auto:type]:type
这种形式来定义动态类型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
interface StringName {
[name: string]: string;
}
let nameDate: StringName;
nameDate = {
怀柔水库: "1",
密云水库: "1",
黑山水库: "1",
};
TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引。
nameDate[0] = "密云";
nameDate["1"] = "怀柔";
// nameDate={ '0': '密云', '1': '怀柔' }
myArray[0] = "密云";
myArray["1"] = "怀柔";
// myArray=[ '密云', '怀柔' ]
最后,你可以将索引签名设置为只读,这样就防止了给索引赋值。
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
类类型
TypeScript
能够用接口来明确的强制一个类去符合某种定义。
这里我们通过implements
去给类定义一个他必须实现的成员类型。当然你也可以一个接口只制定一个规则,通过,
进行接口拼接,使接口的定义更细化。
interface FamilyRespons {
cook(doing: string): void;
clean(doing: string): void;
}
interface Teach {
teach(type: string): void;
}
interface Study {
study(time: num): void;
}
class Father implements FamilyRespons, Teach {
cook(doing: string): void {}
clean(doing: string): void {}
teach(type: string): void {}
}
class Children implements FamilyRespons, Study {
cook(doing: string): void {}
clean(doing: string): void {}
study(time: num): void {}
}
接口继承
和类一样,接口也可以相互继承,可以更灵活地将接口分割到可重用的模块里,并且同样是使用 extends
关键字,而且一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Background {
color: string;
}
interface Content {
width: number;
}
interface BoxStyle extends Background, Content {
borderWidth: number;
}
let boxCss: BoxStyle = {
color: "red",
width: 100,
borderWidth: 1,
};
Part 08 - TypeScript泛型
泛型的定义
泛型是指当我们在定义函数,接口等等时没有去指定他的类型,而是在使用的时候去指定类型的特征。
例如:当我们声明函数时不去声明参数的类型,当我们使用调用时在通过传参的需要去定义传入的类型。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // function identity<string>(arg: string): string
一般情况下我们通过<T>
来代表类型变量。T
帮助我们修改为用户指定传入的类型。
我们定义了泛型函数后,可以通过在<>
中传入我们想要的类型来去使用。如:<string>
<number>
等
泛型变量
当我们在创建泛型函数时,你必须在函数体内把它当做一个通用的类型来进行使用。
例如:以下这种情况就是错误的
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: 类型“T”上不存在属性“length”
return arg;
}
当你作为一个任意类型是你可能传入的是number
因此不存在length属性,所以我们可以通过创建数组的方式去定义这个泛型。
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Success
return arg;
}
当你定义为数组,而数组内部的值为泛型是,那么length方法使用就会很合理了。
泛型接口
首先泛型函数的类型与非泛型函数的类型没什么不同,并且也可以通过泛型来定义接口,通过<>
括起泛型类型。
interface TestInterface<T> {
<T>(arg: T): T;
}
function FunctionInterface<T>(arg: T): T {
return arg;
}
let myIdentity: TestInterface<number> = FunctionInterface;
泛型类
泛型类看上去与泛型接口差不多。 泛型类同样使用 <>
括起泛型类型,跟在类名后面。
class TestClasses<T> {
value?: T;
add?: (x: T, y: T) => T;
}
let doSomeThing = new TestClasses<string>();
doSomeThing.value = 'creatString';
doSomeThing.add = (x, y) => x + y;
泛型约束
以泛型变量举例,想要访问函数体内部arg
的length
属性,但是因为arg
的类型不确定,因此我们需要约束arg
的类型,让它至少包含这一属性。
interface Length {
length: number;
}
function loggingIdentity<T extends Length>(arg: T): T {
console.log(arg.length); // Success
return arg;
}
loggingIdentity(10); // Error 没有length属性
loggingIdentity({ length: 10 });
当T
继承了泛型Length
那么,参数必须包含必须的属性,因此length的使用也自然变得合理。
Part 09 - TypeScript声明合并
合并接口
当两个同名接口进行合并时,实际上就是把双方的成员放到一个同名的接口里。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
同时需要注意,当接口 A
与后来的接口 A
合并时,后面的接口具有更高的优先级。也就是说后来的接口将会重载出现在靠前位置。
interface Box {
scale: number;
height: number;
width: number;
}
结尾
因为目前仅是我个人根据中文官网3.1版本的部分内容进行学习的。因此3.1之后的新特性还没有进行细致的学习和输出。
之后我会根据typeScript官网版本的更新内容进行精读,也会继续输出我学习的内容。作为博客的一个新人希望大佬们能多提供一些建议帮助我更好的提升。如果觉得写的不好希望各位大佬轻喷!至此感谢!