新人第一篇TypeScript文章,陆陆续续写了快半个月。也是我看B站大学小满zs的TS视频后,自己总结的学习笔记;若我有理解错误还请大佬们指出吖。如果是刚接触TS的同学强烈建议动手把代码走一遍,不要只浏览一遍就过了呐。
2023如约而至,即使是前端小白,TypeScript也已经成为必须学习并且能实际应用的一个技术了
当然,技术不是学术,并不是为了考试,而是为了实际应用。所以先了解基础并尽快上手,在实践中再去不断完善和熟练,也许是一种比较节约时间的做法。
正如标题所言,这是一篇快速了解TS的博客,基础却也不失详细;学习后,足够日常的大多需求了学习的思路:为什么TS要这么做,解决的是JS的什么问题? 带着这样的心理去学习和了解,事半功倍
一、基础类型
首先对TypeScript做一些简单的了解:TS是JS的超集,始于JS终于JS,TypeScript 在编译阶段需要编译器编译成纯 JavaScript 来运行。
安装:npm install typescript -g;使用tsc -v命令查看版本,是否安装成功
拓展:命令
tsc xxx.ts会编译ts脚本,生成xxx.js,然后js脚本的运行命令就是node xxx.js利用这些命令可以得到TS编译后的JS代码,从而了解TS的原理。
TS的基础类型:TS是JS的超集,所以JS基础的类型都包含在内
Boolean、Number、String、null、undefined 以及 ES6 的 Symbol 和 ES10 的 BigInt。
这些基本类型就是在定义变量的时候,变量名:数据类型,就可以了;代码示例以及其注意点:
// 1、 Boolean类型
let bol:boolean = false;
// 注意点:使用构造函数 Boolean 通过new创造的对象不是布尔值
// let bol: boolean = new Boolean(1) //报错
// 2、Number类型和BigInt类型
let num:number = 123;
num = Number.MAX_SAFE_INTEGER;
let bigNum:bigint = BigInt(123);
// 注意点:number和BigInt不是一个类型,不可以兼容互相赋值。
// bigNum = 666; //报错,不能将类型“number”分配给类型“bigint”
// 3、String类型
let str:string = 'abc';
// 注意点:可以使用es6字符串模板
str = `${num}`
// 4、null和undefined
let un: undefined = undefined;//定义undefined
let nu: null = null;//定义null
// 5、Symbol类型
let sym:symbol = Symbol();
二、空值类型
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数
// void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数
function voidFn():void {
console.log('test void')
// 不关心函数返回值,不需要return的时候,void
}
// 其声明类型不为 "void" 或 "any" 的函数必须按类型返回值。
function fn():number {
return 123;
}
// void也可以定义undefined 和 null类型
let u:void = undefined
// let n: void = null;
// 注意点:如果配置了tsconfig.json 开启了严格模式,null 不能 赋予 void 类型
// ts严格模式的配置:
// tsconfig.json文件:
// {
// "compilerOptions":{
// "strict": true
// }
// }
一般的面试题会问:Typescript中never 和 void 的区别? void 和 undefined 和 null 的区别?
never类型在文章后面会学习,现在先不管;这里先了解既然void也可以定义undefined 和 null类型,那void 和 undefined 和 null 的区别:
注意:tsconfig.json配置文件中,开启strictNullChecks模式,null 和 undefined 赋值给基础类型是会报错的;所以如果只是为了开发,这部分不管就好,除了
面试的时候其他的地方不需要知道区别是什么的...
// undefined 和 null 是所有类型的子类型,这样写没问题
let u:undefined = undefined;
let str:string = 'a';
str = u
//这样写会报错 void类型不可以分给其他类型
let test: void = undefined
let num2: string = "1"
num2 = test
三、任意类型(any与unknown)
any 类型实际上就是 失去了TS类型检测的作用,和原生js定义的变量一样了 。这也是any的弊端(不过有时候真的好用哈哈哈)
所以TypeScript 3.0中引入的 unknown 类型,即未知类型。相比any更加严格。
any与unknown的相同点:
// unknown 也可以定义任何类型的值
let value: unknown;
value = true; // OK
value = 666; // OK
value = "Hello World"; // OK
any与unknown的区别:
(1)、unknown类型只能赋给unknown/any,即使值是同一类型也不行。any只要值的类型正确,可以任意赋值给其他类型:
let val:unknown = '666';
let str:string = val //即使val的值是'',也不允许。报错:不能将类型“unknown”分配给类型“string”。
// unknown类型只能赋给unknown/any
let any: any = val;
let unk: unknown = val;
将第一句的let val:unknown = '666';改用any就不会报错。
(2)、值为对象时的区别:
// any类型在对象没有这个属性的时,也是可以获取的(这原本也就是JS的特点)
let obj:any = {b:1}
obj.a
// 如果是unknow 是不能调用属性和方法(TS是使用接口来规范对象类型)
let obj2:unknown = {b:1,ccc:():number=>213}
// obj2.b //不允许
// obj2.ccc() //不允许
四、Object,object,{}类型
(1)、Object类型
js中原型链所有的数据类型构造函数都最后指向Object,所以可以是任意类型
let obj1:Object = {a:1,b:2};
obj1 = '';
obj1 = 666;
// Object和any不一样,不能调用属性。
let o:Object = {a:1};
// o.a = 6; // 报错:类型“Object”上不存在属性“a”
(2)、object类型
object则是非基础类型(第一节内容)不能赋值基础类型;一般用于泛型约束
let o1:object = {a:1}
// o1.a // 也报错,类型“object”上不存在属性“a”。
// let o2:object = '' //报错:不能将类型“string”分配给类型“object”。
(3)、{}类型
{}赋值方式同Object
let o1:{} = {};
let o2:{} = '';
五、接口和对象类型
TypeScript 中用接口来定义对象类型
前面的知识可以看出,基本类型的类型约束并不复杂,但是对象类型好像有点麻烦了:写为any类型,就失去了TS的作用,但是不管是unknown、Object、object、{},可以定义对象,但都限制了使用属性和方法(这也是为了规范引用类型)。
所以,TS是使用了接口来规范对象类型
我们通常这样使用JS定义对象:
let obj = {
name:'xxx'
}
在TS中,定义对象首先要定义接口来规范对象,然后再定义对象:
注意点:
1、使用interface关键字定义接口
2、多个接口同名,不是后定义覆盖前,而是合并
3、接口中定义好属性,后面定义对象的时候,即有了类型、数量的约束,多写/少写/类型不一致 都不允许
interface A{
name:string
}
interface A{
num:number,
fn():void
}
let obj:A = {
name:'xxx',
num:123,
fn:()=>{}
// age:1 // 两个A接口合并后都没有age属性,报错
}
obj.name = '';
interface 也可以用来描述函数类型,代码如下:
interface ISum {
(x:number,y:number):number
}
const add:ISum = (num1, num2) => {
return num1 + num2
}
拓展:上述TS对象已经正常使用了,但是开发中,往往数据是动态的,数据是后台获取的。
多选/少写/类型不一致 都报错有点过分严格了。
TS提供了两个操作来解决这个问题:
(1)、可选操作符:?
(2)、当属性名不知,类型也不知,个数也不确定 那怎么提前定义ts接口?可以定义[propName: string]: any;
允许添加新的任意属性
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
b?:string,
a:string,
[propName: string]: any;
}
// 接口描述:a属性必有,b属性可选,允许添加新的任意属性
const person:Person = {
a:"213",
c:"123",
d:'',
e:''
}
接口的readonly 只读属性;在初始化后,是不允许被赋值的只能读取
interface Person2 {
readonly a: string
}
const person2: Person2 = {
a: "123"
}
person2.a
// person2.a = '666' //报错:无法为“a”赋值,因为它是只读属性。
interface 还有一个响亮的名称: duck typing(鸭子类型)。
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
只要数据满足了 interface 定义的类型,TS 就可以编译通过。
interface FunctionWithProps {
(x: number): number
fnName: string
}
const fn: FunctionWithProps = (x) => { return x }
fn.fnName = 'hello world'
FunctionWithProps 接口描述了一个函数类型,还向这个函数类型添加了 name 属性,这看上去完全是四不像,但是这个定义是完全可以工作的。
这就是 duck typing 和 interface,非常的灵活。
六、数组类型
基本类型讲了,接口+对象类型也讲了,但是数组是特殊的对象
三种方式定义:
// 1、类型[ ]
let arr0:number[] = [1,2,3] //里面元素只能是number
let arr1:any[] = [1,'a',{}] //任意类型的数组
// 2、数组泛型
let arr2:Array<number> = [1,2,3] //效果同 arr:number[]
// 3、还可以用接口
interface NumberArray {
[index: number]: number;
}
let arr3: NumberArray = [1];
多维数组:
let data:number[][] = [[1,2], [3,4]];
arguments类数组:
接口:IArguments
IArguments 是 TypeScript 中定义好了的类型,内置的接口
function Arr(...args:any): void {
console.log(arguments);
// let arr:number[] = arguments //arguments 是类数组,不能赋值给数组类型
let arr:IArguments = arguments;
}
Arr(111, 222, 333);
IArguments关键字的源码就是一个接口:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
七、TS的函数
参数的个数、类型(不能多或少,除非两种情况: 可选操作符/有参数默认值 则可以少。这点和接口的逻辑一致,用ts的?操作符);以及返回值的类型(没有返回值就是void)
let fn1 = (str1:string, str2?:string, str3:string='参数默认值'):string=>{
return '666';
}
fn1('');
fn1('','','');
// fn1();//少了,不允许
// fn1('','','','');//多了,不允许
注意:由于ts限制的参数个数,于是ts也拥有了 函数重载 即函数名字相同,而参数不同,从而调用了不同的逻辑。js是没有函数重载的
八、联合类型 | 交叉类型 | 类型断言
(1)、联合类型
TS的 | 操作符
在开发中,有时候无法提前固定好数据的类型
eg:的手机号通常是11位数字,为number类型 这时候产品说需要支持座机,有 "-" 连接,这个时候必须为string
//联合类型,同时支持number | string
let myPhone: number|string = '010-820'
myPhone = 18080808080;
TS数组的联合类型
只有一种,好办:const array:number[] = [1,2,3]
多种:
不能用元组来初始化
元组只是push有用是联合类型,初始化的话不行,是一一对应的:
只是push的时候是联合类型:
但是这还是没有实现初始化的时候想要用联合类型
用小括号:
const array:(number|string)[] = [1,'',2,3,'abc'] //正确写法
(2)、交叉类型
交叉类型即为合并,ts内置的 & 操作符
interface People {
age: number,
}
interface Man{
sex: string
}
const xiaoman = (man: People & Man) => {
console.log(man.age)
console.log(man.sex)
}
// xiaoman({age: 18}); // 报错:缺少属性 "sex",但类型 "Man" 中需要该属性。
xiaoman({age: 18,sex: 'male'});
(3)、类型断言
TS的关键字 as 只是用来解决一些特殊场景下TS不报错,并不具有改变能力
例子:(配合联合类型)
let fn = (phone: number | string)=>{
console.log(phone.length);
}
上述代码的函数中使用了参数的length属性,但是number类型是没有这个属性的。
number无length而string有,无法确定,不定时的错误。ts报错:
所以:
let fn = (phone: number | string)=>{
console.log((phone as string).length); //利用类型断言避免报错
}
fn('123') // 3
fn(123) // undefined
这样程序不会报错,而是返回undefined
从结果可以看到,参数为number的时候,结果为undefined,也证明了“类型断言”只是用来解决一些特殊场景下TS不报错,并不具有改变能力(没有因为phone as string而转化了参数的类型)
类型断言是不具影响力的,即不会改变数据。只是通过「欺骗」TypeScript 编译器从而不报错;在编译过程中会删除类型断言
九、内置对象
// 七、内置对象
// 其他的看他博客去,有点水
// 重点在于Promise
// 语法规则:Promise<T> 类型
function promise():Promise<string>{
return new Promise<string>((resolve,reject)=>{
// resolve(1) //Promise<T>,T的类型就是规定了resolve()参数的数据类型
resolve('1')
})
}
promise().then(res=>{
console.log(res)
})
十、Class类
(1)、TS类的基本写法
JS的类是这样定义的,使用class关键字
class Person {
constructor(num, msg){ //constructor构造函数
this.num = num;
this.msg = msg;
}
}
let per = new Person(1,'message')
TS的类也是class关键字,只是加上类型
// TS的写法:
class Person {
num:number
msg:string
constructor(num, msg){
this.num = num;
this.msg = msg;
}
}
let per = new Person(1,'message')
(2)、TS类的三个修饰符:public、private、protected
用来修饰TS的类的内部的变量;类中的变量的访问权限分内部,子类,外部
- public:内部,子类,外部
- protected:内部,子类
- private:内部
class Person {
public num:number
private msg:string
protected val:string
constructor(num, msg, val){
this.num = num;
this.msg = msg;
this.val = val;
}
}
// 子类:
class Student extends Person {
constructor(){
super(1,'',''); //super会调用父类的constructor构造函数
this.val; //protected,可在内部,子类
// this.msg; private,只有内部
}
}
let per = new Person(1,'message','val');
per.num //public,可在内部,外部,子类
// per.msg 报错
// per.val 报错
(3)、再补充一个修饰符,static,静态属性和方法
实例属性、方法 与 静态属性、方法 之间都不可以互相调用。
class Person {
//静态属性:
static sta:string
//静态方法:
static fn(){
// this.num //静态函数中也不能拿到给实例的构造函数的方法
this.sta
}
constructor(){
// this.fn //构造函数中不能访问静态方法和属性
this.fn2
}
//实例方法:
fn2(){}
}
let per = new Person();
Person.sta;
// per.sta; //报错
(4)、用接口去约束类
关键字 implements 可使用多个接口来约束,用逗号隔开。继承还是用extends
interface PersonClass {
get(type: boolean): boolean
}
class A {
constructor() {}
}
class B implements PersonClass{
constructor() {}
// 不定义这个函数则ts报错,因为接口中规定了名称、个数、和类型。这就是TS的“约束”
get(type:boolean) {
return type
}
}
class C extends A implements PersonClass {
constructor() {
super()
}
get(type:boolean) {
return type
}
}
(5)、抽象类
除了使用接口来约束类,还可以使用抽象类来约束。关键字abstract
抽象类不能直接用来new实例化,只能被继承,然后由派生类去实例化
抽象类里面的抽象方法只写约束,不实现;继承的子类中需要去实现这个抽象方法(定义的抽象方法必须在派生类实现)
// 抽象类:
abstract class Person{
name:string
constructor(name){
this.name = name
}
// abstract getName(){} //只写约束,不实现;继承的子类中
//需要去实现这个抽象方法(定义的抽象方法必须在派生类实现)
abstract getName():string
setName(newName){ //普通实例方法
this.name = newName
}
}
// 派生类
class A extends Person{
constructor(){
super('名字')
}
getName(){
return this.name
}
}
// 由派生类提供实例化
let a = new A();
console.log(a.getName()) //名字
a.setName('新名字')
console.log(a.getName()) //新名字
使用抽象类来约束类明显比使用接口来约束更直观。
抽象类的作用:作为类的模板,规范了其子类定义的标准,让代码更加规范。
类比于之前学的接口,接口和抽象类的出现主要是为了添加参数、类型等的限制,来规范代码
类,既可以用接口来规范,也可以用抽象类来规范
十一、元组类型
前面学习的数组类型的每个元素只能是同一种类型:
let array:number[] = [1,2]
而元组可以设置不同类型:
let array2:[string, number] = ['',2]
//注意:当元组越界时,越界的元素他的类型被限制为 联合类型
array2.push('') //可以
array2.push(1) //可以
array2.push(true) //不行,array2的联合类型是 string|number
当需要数组的元素类型不一样时,使用TS的元组来规范该数组。
十二、枚举类型
javaScript中是没有枚举的概念
TS帮我们定义了枚举这个类型,通过enum这个关键字
使用枚举类型可以允许我们定义一些带名字的常量,也可以清晰地表达意图或创建一组有区别的用例。
(1)、数字枚举
数字枚举也是增长枚举,会自动+1;也可以定义初始值
enum Types {
Red,
SkyBlue
}
// 默认都是从0开始
console.log(Types.Red) // 0
console.log(Types.SkyBlue) // 1
enum Type2 {
Red = 100,
SkyBlue
}
console.log(Type2.Red) // 100
console.log(Type2.SkyBlue) // 101
(2)、字符串枚举
//花括号里面用=号也挺少见的
enum Types{
Red = 'red',
Green = 'green',
BLue = 'blue'
}
console.log(Types.Red) // red
console.log(Types.Green) // green
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,
这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
(3)、异构枚举
枚举可以混合字符串和数字成员:
enum Types{
No = "No",
Yes = 1,
A //在数字枚举之后,所以A是递增
}
console.log(Types.No) // No
console.log(Types.Yes) // 1
console.log(Types.A) // 2
枚举的应用场景
应用场景:后端返回的字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性,如下:
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
但有时候后端给你返回的数据状态是乱的,就需要我们手动赋值。
比如后端说 Buy 是 100,Send 是 20,Receive 是 1,就可以这么写
enum ItemStatus {
Buy = 100,
Send = 20,
Receive = 1
}
console.log(ItemStatus['Buy']) // 100
console.log(ItemStatus['Send']) // 20
console.log(ItemStatus['Receive']) // 1
十三、类型推论|类型别名
(1)、类型推论:
let str = '';
str = 1 //报错
虽然声明的时候没有定义类型,但是初始化赋值'',所以类型推论为字符串,不能赋值其他类型
没有初始化的话,则类型推论为any:
let a;
a = '';
a = 1;
(2)、类型别名
type 关键字,就算给类型取个名字
type str1 = string
type str2 = string | number
let a1:str1 = ''
let a2:str2 = 1
十四、never类型
TypeScript 使用 never 类型来表示不应该存在的状态
例如 交叉类型 常用于两个interface接口的交叉,来规范对象。如果用于变量就不合适:
type aaa = string;
type bbb = string | number;
// 鼠标移上,ccc被推断为了never,因为一个变量不可能既是string又是number
type ccc = string & number;
记个面试题:Typescript中never 和 void 的区别?
void 表示没有任何类型(可以被赋值为 null 和 undefined)。never 表示一个不包含值的类型,即表示永远不存在的值。拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。
十五、泛型
泛型在TypeScript 是很重要
因为TS代码在定义的时候就需要确定类型,有些场景就不太合适
所以使用泛型就是定义的时候不写死类型,调用/使用的时候再去固定数据的类型;程序使用泛型更灵活,提高了复用性
所谓约束,即改掉JS的松散规则,使用TS严格规则,不符合TS的风格需要报错,这就是约束
用函数举个例子:
function num(a:number, b:number) :Array <number>{
return [a, b]
}
function str(a:string, b:string) :Array <string>{
return [a, b]
}
num(1,2);
str('a','b');
上述TS代码中两个函数功能完全一致,只因为类型不一致,就需要定义两个函数,这明显不是优秀的代码
所以可以用泛型来优化:
(1)、函数泛型
上述函数的问题,用函数泛型可以解决:
//例一(参数同一类型):
function fn<T>(a:T, b:T) :Array<T> {
return [a, b];
}
fn<string>('a','b')
fn<number>(1,2)
//例二(参数不同类型):
function fn2<T,U>(a:T,b:U) :Array<T|U> {
return [a,b];
}
fn2<string,number>('a',1);
泛型的语法为函数名字后面跟一个<参数名>参数名可以随便写 例如我这儿写了T
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)
多敲几遍就嘎嘎熟练
(2)、泛型约束
例如需要返回长度length,但不是所有的类型都有length,所以要做泛型约束避免错误,使得函数更健全:
function fn<T>(a:T){
return a.length;
}
上面TS代码中函数报错,T类型没有length;
所以需要用接口约束泛型T(extends还能这样用):
interface Len {
length:number
}
function fn<T extends Len>(a:T){
return a.length;
}
fn<string>('abc'); //OK
// fn<number>(1); // 报错,number被Len接口约束了;使得程序更健全
(3)、使用keyof 约束对象的属性
因为JS是动态语言可以直接增加属性,但是TS就不允许(其实其他语言也不允许,JS太松散灵活)
所以导致:
function fn<T>(obj:T, key){
return obj[key]
}
let o = {a:1, b:2}
fn(o, 'a');
fn(o, 'c') //并未报错
这就出问题了,o这个对象没有c属性,但是没提示报错
所以需要约束,来避免这种不确定性,TS定义了keyof这个关键字来约束:
function fn2<T,K extends keyof T>(obj:T, key:K){
return obj[key]
}
let o2 = {a:1, b:2}
fn2(o, 'a');
// fn2(o, 'c') //报错:Argument of type '"c"' is not assignable to parameter of type '"a" | "b"' (这个报错也能看出上述的原理,联合类型)
原理:<T,K extends keyof T>,K通过keyof操作符会获取T类型的所有键,即keyof T返回了一个联合类型;然后再用extends关键字约束K类型必须为keyof T返回的联合类型的子类型
(4)、泛型类
和泛型函数差不多,就是定义的时候不定死类型,实例化对象的时候再去写类型
class Sub<T>{
a:T[] = [];
}
// 实例化number:
let obj = new Sub<number>();
obj.a = [1,2]
obj.a = [3,4,5]
// obj.a = ['a','b'] //报错
// 实例化string:
let obj1 = new Sub<string>();
// obj1.a = [1,2] //报错
obj1.a = ['a']
(5)、type 和 interface 都可以定义函数类型
type 这么写:
type Print = <T>(arg: T) => T
const printFn:Print = function print(arg) {
console.log(arg)
return arg
}
interface 这么写:
interface Iprint<T> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint<number> = print
十六、配置文件:tsconfig.json
在项目中,TS的配置文件是tsconfig.json
配置项很多,这里了解常用的配置即可,其他的用到的时候再去搜索
生成tsconfig.json 文件(注意:json文件里面只能用"",不能'')
这个文件是通过tsc --init命令生成的
常用的几个配置:
include
不写这个配置则是默认编译当前目录下所有的ts文件,写的话就是一个数组,将哪些TS文件编译为JS文件
"include": ["./index1.ts","./index2.ts"]
exclude
即include反过来,格式一样
target
指定编译后的js 的版本,例如es5 or es6
allowJS
是否允许编译js文件;默认当然允许,为true
removeComments
是否在编译过程中删除文件中的注释
strict
是否开启严格模式
十七、命名空间
namespace
TypeScript 与ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块
相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的
所以,不同文件定义了同名变量,也会报错,因为这是全局的
例如我们在在一个 TypeScript 工程项目下建立一个文件 1.ts,声明一个变量a,如下:
const a = 1
然后在另一个文件同样声明一个变量a,这时候会出现错误信息:
为了避免命名冲突,有两种解决办法:
- import或者export引入模块系统即可;这里typescript语法与ES6一致,不赘述
- 命名空间
只允许在命名空间或模块的顶部声明命名空间。即在ts文件中用namespace
namespace A{
export const a = 1
}
console.log(A.a)
原理是包了一层function,上述TS代码编译后的JS代码是这样:
var A;
(function(A){
A.a = 1;
})(A || (A = {}))
命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。
但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用(所以就是,了解即可,用到再说)
十八、装饰器
Decorator 装饰器是一项实验性特性,在未来的版本中可能会发生改变
所以不学以后再说 没错我就是懒
TS入门搞完啦;以后要进阶再去学习新的,目前这些够用。