TS和JS的区别
ts | js | |
---|---|---|
函数返回值 | 有类型 | 无 |
函数参数 | 必填和可选参数 | 所有参数都是可选的 |
函数重载 | 有 | 无 |
typeof | 推算一个变量的类型 | 获取变量的类型 |
extends | 在 interface 中表示类型扩展,在条件类型语句中表示布尔运算,在泛型中起到限制的作用,在 class 中表示继承 | 在 class 中表示继承 |
window | window.a = {}报错,(window as any).a = {};正确 | window.a = {}没错 |
基础基础
ts的类型
object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。
除了js的类型,还有Tuple enum Any void Never unknown
string类型的数组,建议双引号
Tuple
元祖类型属于数组的一种,可以指定多种类型,个人觉得比数组功能强大
Void
function voidFun(): void {
console.log("hello");
}
voidFun() //hello
注:声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined 或 null
let unusable: void = undefined;
let unusable1: void ;
console.log(unusable); //undefined
console.log(unusable1); //undefined
let unusable: void = null;
//unusable提示 :type 'null' is not assignable to type 'void'
Null和Undefined
默认情况下 null 和 undefined 是所有类型的子类型,然而,如果指定了--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自的类型
。
let a: undefined = undefined;
let b: null = null;
Never
- never 类型表示的是那些永不存在的值的类型,例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
- 返回never的函数必须存在无法达到的终点
- 使用 never 可避免出现新增联合类型没有对应的实现,
目的就是写出类型绝对安全的代码
。
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
改为
type Foo = string | number | boolean;
controlFlowAnalysisWithNever中else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。确保代码绝对安全。
null undefined never
var a:number
console.log(a) //有--strictNullChecks标记,编译不过,否则编译通过
定义但未赋值就是undefined
var a:number|undefined
console.log(a) //undefined
null同理
没有--strictNullChecks标记,null与 undefined可以赋值给任何类型。 null与 undefined是所有其它类型的一个有效值
枚举(enum):支持数字的和字符串
枚举类型是定义标识,用于定义一些固定值。
一般首字母大写
- 数字枚举
- 不赋值默认就是索引值,注意索引是从0开始
- 其余的成员会从 初始化的值 开始自动增长
- 在有初始化值之前元素的第一个是0开始
比如pay_status 0未支付 1支付 2交易成功
enum Flag {success=1, error=0}
var f:Flag = Flag.success
console.log(f) //1
enum Color{red,blue,green}
var c:Color = Color.red
console.log(c) //0
不赋值默认就是索引值,注意索引是从0开始
enum Color{red,blue=5,green}
var c:Color = Color.red
var c1:Color = Color.blue
var c2:Color = Color.green
console.log(c,c1,c2) //0 5 6
green没有值以上一个为基准
- 字符串枚举:相比数字枚举更具有可读性
字符串枚举里,
每个成员都必须用字符串字面量
,或另外一个字符串枚举成员进行初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
- 异构枚举:可以混合number和string类型,但是一般不这么使用
enum Test {
No = 0,
Yes = "YES",
}
有时候表示类型,有时候表示值
表示值:
enum Pos {
LEFT,
RIGHT
}
let p : Pos = Pos.RIGHT
console.log(p) //1
enum Pos {
LEFT = 5,
RIGHT
}
let p : Pos = Pos.RIGHT
console.log(p) //6
enum Pos {
LEFT = '1',
RIGHT = '2'
}
let p : Pos = Pos.RIGHT
console.log(p) // '2'
enum Pos {
A,
B,
LEFT = '1',
RIGHT = '2',
}
let p : Pos = Pos.RIGHT
console.log(p) //'2'
console.log(Pos[0]) // 'A'
表示类型
enum ActiveType {
Active,
Inactive
}
type KeyOfType = keyof typeof ActiveType // "Active" | "Inactive"
let test: KeyOfType = 'Active' || 'Inactive' //ok
高级类型
- 交叉类型:是将多个类型合并为一个类型
我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!)
Person & Serializable & Loggable
- 联合类型
padding: string | number
- 类型保护与区分类型
is: 此关键字用作用户类型防护,用来告诉 TS 如何辨别类型
interface Animal {
walk: () => {}
}
// cat is Animal用来告诉用户TS,cat是Animal类型
function isAnimal(cat: any): cat is Animal {
return (cat as Animal).walk !== undefined;
}
let cat = {}
if (isAnimal(cat)) {
cat.walk() // OK
} else {
pet.walk() // 类型“Bird”上不存在属性“swim”
}
其他可用来判断类型的关键字还有 typeof,instanceof, in 等等。
typeOf
typeof类型保护*只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
function padLeft(value: string, padding: string | number) {
// 不必定义一个函数来判断类型是否是原始类型
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
- instanceof instanceof类型保护是通过构造函数来细化类型的一种方式 instanceof的右侧要求是一个构造函数,TypeScript将细化为:
此构造函数的 prototype属性的类型,如果它的类型不为 any的话 构造签名所返回的类型的联合
可选参数和可选属性 :一定是可选,也就是带?
使用了 --strictNullChecks,可选参数会被自动地加上 | undefined
function f(x: number, y?: number) {
return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
可选属性也会有同样的处理:
class C {
a: number;
b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'
使用类型断言手动去除null或 undefined,语法是添加 !后缀
identifier!
从 identifier的类型里去除了 null和 undefined
类型别名type:会给一个类型起个新名字
-
类型别名有时和接口很像,但是
可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型
。 -
类型别名也
可以是泛型
- 我们可以添加类型参数并且在别名声明的右侧传入
type Container<T> = { value: T };
- 可以使用类型别名来在属性里
引用自己
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
注:类型别名不能出现在声明右侧的任何地方
type Yikes = Array<Yikes>; // error
接口interface 和 类型别名type的区别
interface创建了一个新的名字
,可以在其它任何地方使用。type不创建新名字
interface总是会显示在编译器的诊断信息和代码编辑器的智能提示中,type的只在特定情况下显示
比如,错误信息就不会使用别名。
type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
在编译器中将鼠标悬停在 interfaced上,显示它返回的是 Interface
悬停在 aliased上时,显示的却是对象字面量类型
type不能被 extends和 implements
(自己也不能 extends和 implements其它类型)。
interface可以继承其他interface、类等对象类型,但type不支持继承
因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,应该尽量去使用接口代替类型别名。
-
interface类型可以合并,type不能合并
-
如果你无法通过interface来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用type。
类型别名type能够表示非对象类型,而接口interface只能表示对象类型,因此表示原始类型、联合类型、交叉类型只能使用type
总结描述多种类型的方式:
- interface
- 联合类型
- 交叉类型
- 元组
- type
type 可以通过多种方式得到新的类型 typeof Tuple 对象
索引类型查询操作符 keyof
用法:keyof T
索引访问操作符T[K]
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name]; // o[name] is of type T[K]
}
type Teacher = {
id: string;
name: string;
};
type TeacherKeys = keyof Teacher; //"id" | "name"
let a: TeacherKeys = 'id' || 'name' // ok
let a: TeacherKeys = 'other' //error
keyof和 T[K]与字符串索引签名联合使用,keyof T会是 string`,并且 T[string]为索引签名的类型:
interface Map<T> {
[key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number
keyof还可以做映射类型,具体可以看官网 www.tslang.cn/docs/handbo…
keyof typeof联合使用
enum ActiveType {
Active,
Inactive
}
type KeyOfType = keyof typeof ActiveType // "Active" | "Inactive"
如果要获取 enum 的 key 类型,需要先把它当成值,用 typeof 再用 keyof。
enum ActiveType {
Active,
Inactive
}
type KeyOfType = keyof ActiveType // "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
let test : KeyOfType = 'toString'
console.log(test) //'toString'
类型断言2种方式
-
- 尖括号
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
-
- as语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的
。
接口
接口的作用,在面向对象的编程中,接口是一种规范的定义,起到一种限制和规范的作用。
可以对批量方法进行约束 www.tslang.cn/docs/handbo… 可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
把整个ReadonlyArray赋值到一个普通数组也是不可以的
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
a = ro; // error!
readonly vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用readonly
- 额外的属性检查 绕开定义出错的方式:
- 断言
- 索引签名
- 将这个对象赋值给一个另一个变量
类型断言能绕开的问题:
- 可索引的类型(可以对对象和数组的约束)
TS支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是
数字索引的返回值必须是字符串索引返回值类型的子类型
。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
字符串索引签名能够很好的描述dictionary模式,并且它们也会确保所有属性与其返回值类型相匹配。
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
可以将索引签名设置为只读
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
- 接口继承接口
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
- 混合类型
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {}
- 接口继承类 当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
class Location {
}
类
www.tslang.cn/docs/handbo… 重写父类的方法
protected方法;不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。
如果有抽象方法,必须要有抽象类。抽象类的子类必须实现抽象类里面的抽象方法。
- 把类当做接口使用
为什么要静态方法? 一般用得不多 静态方法里面没法直接调用类里面的实例属性。可以直接调用类里面的静态属性。
注意:类类型接口:对类的约束 和 抽象类有点相似
函数
- 可选参数和默认参数
可选参数必须跟在必须参数后面
与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值
。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
- 剩余参数
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。
- 箭头函数
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
- 函数重载 在定义重载的时候,一定要把最精确的定义放在最前面
泛型(类型变量T)
泛型就是解决类 接口 方法的复用性、以及对不确定数据类型的支持,还有类型校验
。any没有类型校验。
种类:泛型类 泛型函数 泛型接口
-
泛型是一种函数,使用的T是类型变量
-
为什么使用泛型,不使用any? 不会丢失信息。
使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。也就是使用了泛型(类型变量,只用于表示类型而不是值)
function identity<T>(arg: T): T {
return arg;
}
- 泛型函数后,可以用两种方法使用:
- 传入所有的参数,包含类型参数:
let output = identity<string>("myString"); // type of output will be 'string'
明确的指定了T是string类型
,并做为一个参数传给函数,使用了<>括起来而不是()
更普遍
。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型
let output = identity("myString"); // type of output will be 'string'
类型推论帮助我们保持代码精简和高可读性
。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
泛型的应用场景 啥时候使用索引签名 可选参数
-
使用泛型变量
-
泛型类型
-
泛型类 类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
-
泛型约束
extends
在泛型约束中使用类型参数 在泛型里使用类类型 -
泛型接口 写法一:
interface Config{
<T>(value:T):T
}
var getData:Config=function<T>(value;T):T{
return value
}
getData<string>('张三')
写法二:
interface Config<T>{
(value:T):T
}
function getData<T>(value;T):T{
return value
}
var myGeData:Config<string> = getData
myGeData('张三')
- 把类作为参数类型的泛型类
- 定义类
- 把类作为参数来约束数据传入的类型
案例: 定义一个User的类这个类的作用就是映射数据库字段,然后定义一个MysqlDb的类用于操作数据库,然后把User类作为参数传入到MysqlDb中
每操作一数据表,就得重新传入数据类型,所以最好用泛型,造成代码重复
泛型类
//操作数据库的泛型类
class MysqlDb<T>{
add(info:T):boolean{
console.log(info)
return true
}
update(info:T,id:number):boolean{}
}
//定义一个User类和数据库映射
class user{
username:string|undefined
}
var u = new User()
u.username='张三'
class user1{
username:string|undefined
}
var u1 = new User1()
u1.username='张三'
var db = new MysqlDb<User>()
db.add(u)
db.add(u1)
。。。可以添加多个
命名空间
主要用于组织代码,避免命名冲突
namespace A{
class Dog{}
}
let g = new A.Dog()
命名空间和模块的区别
命名空间:主要用于组织代码,避免命名冲突 模块:侧重代码的复用,一个模块里可能会有多个吗命名空间。
索引签名
利用「索引签名」
方式可以为对象动态分配属性
(字符串键名和任何类型键值),类似于js中的let obj = {},obj.name = 'hannie'
interface LooseObj {
[key: string]: any
}
let developer: LooseObj = {};
developer.name = "hannie";
「索引签名」
可以用来定义对象内的属性、值的类型,例如定义一个 React 组件,允许 Props 可以传任意 key 为 string,value 为 number 的 props。
interface Props {
[key: string]: number
}
<Component count={1} /> // OK
<Component count={true} /> // Error
<Component count={'1'} /> // Error
如何为对象动态分配属性
-
- 索引签名
interface LooseObj {
[key: string]: any
}
let developer: LooseObj = {};
developer.name = "hannie";
-
- 定义可选的属性
interface Developer {
name: string;
age?: number;
[key: string]: any
}
let developer: Developer = { name: "hannie" };
developer.age = 18;
developer.city = "beijing";
-
- Record<keysType,valuesType>:定义键类型为 keysType、值类型为 valuesType 的对象类型
interface Developer extends Record<string, any> {
name: string;
age?: number;
}
let developer: Developer = { name: "hannie" };
developer.age = 18;
developer.city = "beijing";
泛型的反向推导
type V<T> = T
type NumberValue = V<number>
var a:NumberValue = 1
console.log(a)
索引签名
type Test = {
foo: number;
bar: string
}
type N = Test['foo'] // number
// 类型可以重新定义吗?
条件类型
type IsNumber<T> = T extends number ? number : string;
type A = IsNumber<2> // yes
type B = IsNumber<'3'> // no
let b:B = '2'
console.log(b)
type TypeName<T> = T extends string
? "string"
: T extends boolean
? "boolean"
: "object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type Value<T> = T
type NumberValue = Value<number>
type V<T> = T
type NumberValue1 = V<number>
type Test = {
foo: number;
bar: string
}
type N = Test['foo'] // number
// 类型可以重新定义吗?
var a:NumberValue = 1
console.log(a)
type IsNumber<T> = T extends number ? number : string;
type A = IsNumber<2> // yes
type B = IsNumber<'3'> // no
let b:B = '2'
console.log(b)
type TypeName<T> = T extends string
? "string"
: T extends boolean
? "boolean"
: "object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
其他细节
readonly vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const,若做为属性则使用readonly。
const使用场景:如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们
对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
{}|object|Object
- Object 接口定义了 Object.prototype 原型对象上的属性
Object描述了所有对象的共享属性,js允许在原始值直接访问这些方法
'str'.valueOf()
除了undefined、null,其他任何值都可以赋值给Object类型
//正确
let obj: Object;
obj = {x:0};
obj = true;
obj = 'hi';
obj = 1;
//编译错误
obj = undefined;
obj = null;
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // // Object prototype may only be an Object or null: undefined
Object.create(11); // Object prototype may only be an Object or null: 11
Object.create(true); // Error
Object.create("oops"); // Error
- object 表示的是常规的 Javascript 对象类型,非基础数据类型。ts早版本无法表示非原始类型,后来object填补了这个功能上的缺陷。
objet类型仅能够给以下三种类型:
- 顶端类型any和unknown
- Object类型
- 空对象类型字面量“{}”
const aa: object = {}
const bb: any = aa
const cc: unknown = aa
declare function create(o: object): void;
create({ prop: 0 }); // OK
create(null); // Error
create(undefined); // Error
create(42); // Error
create("string"); // Error
create(false); // Error
create({
toString() {
return 3;
},
}); // OK
报错,只能对对象创建
- {TypeMember} ,叫做对象类型字面量,表示的非 null,非 undefined 的任意类型。 类型成员可以是:属性签名、调用签名、构造签名、方法签名、索引签名
举例可选属性
let point: {x:number,y?:number}
//正确
point = {x:0}
point = {x:0,y:0}
//错误
point = {x:0,y:0,z:0}
空对象类型字面量{}和全局的Object类的区别: 单从行为上来看两者可以互换使用。比如
let a: Object = 'hi';
let b: {} = 'hi'
//互相允许赋值
a = b
b= a
Object用于描述对象公共的属性和方法,它相当于一种专用类型,因为程序中不应该讲自定义变量、参数等类型直接声明为Object类型。
空对象类型字面量“{}”强调的是不包含属性的对象类型,同事也可以作为Object类型的代理来使用。
小结:
- object类型更适合很严谨的场合,因为只能给非原始类型使用,很具体
- Object是结构函数,一般作为提供对象公共属性和方法
interface User {
id: string
}
interface User {
name: string
}
interface User {
other: object
}
const user = {} as User
console.log(user.id);
console.log(user.name);
console.log(user.other);
user.other = {a:1}
interface User {
other: {}
}
user.other = null 报错
interface User {
other: Object
}
const user = {} as User
user.other = {toString(){return 1}} //报错
type User2 = string
let a = '' as User2
a = '2'
let isDone: boolean = false;
var a : string = '1'
let b : number = 1
总结
可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件
总结this箭头函数实例可以推翻结果
在TypeScript里使用JSX时,只有as语法断言是被允许的。
尽可能地使用let来代替var
typescript.bootcss.com/functions.h…
类型键入
类型键入允许 Typescript 像对象取属性值一样使用类型
type Teacher = {
id: string
sthdentList: {
fristName: string
lastName: string
}[]
}
type UserIdType = Teacher['id'] // string
type SthdentList = Teacher['sthdentList'] // { fristName: string; lastName: string; }[]
type Friend = SthdentList[number] // { fristName: string; lastName: string; }
UserIdType、SthdentList、Friend是得到的新类型
type Tuple = [number, string]
type First = Tuple[0] // number
type Second = Tuple[1] // string
同样也可以用于元祖
总结
当类型不确定的情况下,考虑使用泛型
日常开发中经常用到的泛型有 Promise、Array、React.Component 等等。
同泛型一样,一般小项目用不到装饰器,但是看别人的框架要能看懂。
any放弃了类型检测,泛型可以限制传入什么类型并返回什么类型,但是可以支持不特定的数据类型。
开发的过程中可以尝试使用 keyof enum Tuple 类型判断 type interface 函数 class,泛型要能看得懂