TypeScript学习
1.基础类型
TypeScript和JavaScript支持的数据类型差不多
-
布尔:boolean
let flag: boolean = false
-
数:number
可以表示整数,浮点数,支持2进制,8进制,10进制,16进制
let num1: number = 1
-
字符串:string
let str = "a"
let str2 = `${a}`
-
数组:
Array <number>
或者number[]
-
元组: tuple
let a: [number, string] = [1, "a"]
-
枚举enum
enum Color { Red, Green, Yellow, Purple }
enum Test { Red, Green=1, Yellow }
// Test.Red = 1, Test.Green = 1, Test.Yellow = 2
注意枚举类型最好不要赋值为字符串,否则可能会产生意想不到的情况
-
any
表示类型不确定,或者在一个数组中有多种类型,你只知道一部分类型,不能确定全部就可以用any
-
void
如果一个函数没有返回值,则可以用void来定义,如果一个类型为void,那么只能被赋值为undefined或者null
-
null和undefined
当我们配置了:strickNullChecks:false,就可以将null和undefined赋值给任何类型,但是这样做不太妥,我们容易忽略null和undefined类型,会偏离预期,所以最好是设置为true。
-
never
表示永远不存在的值,你可能会觉得很奇怪,不存在还要这个干啥???
一般的应用场景:
① 抛出异常,没有返回值的函数表达式。
② 无法到达终点的函数。
③ switch的default情况设置为never可以帮助我们检查漏掉的case类型。
2. 泛型
当我们想要根据实际的情况来决定变量的类型时,我们就可以使用泛型,这个东东在java中也有,
好处:
就是函数和类可以轻松地支持多种类型,增强程序的扩展性,不用谢多余的函数重载,灵活控制类型之间的约束。
注意点:
静态成员不能引用类类型参数
class Person {
static name:string;
}
2.1 泛型变量
function identity<T>(arg: T): T {
return arg;
}
但是如果是这样的:
function identity<T>(arg: T): T {
return arg.length;
}
则会报错,因为编译器无法知道T是什么类型,万一是number,那么它一定没有长度,我们可以改写:
function identity<T>(arg: T[]): T[] {
return arg.length;
}
// or
function identity<T>(arg: Array<T>): T[] {
return arg.length;
}
2.2 泛型函数和接口
根据传入的类型,返回相应的类型对应的值
function log<T>(value: T): T {
return value
}
也可以使用type来定义
type Log = <T>(value:T) => T
let myLog: Log = log
也可以使用interface
interface Log<T> {
(value: T): T
}
let myLog: Log<number> = log
myLog(1)
2.3 泛型类和泛型约束
我们也可以对class使用泛型
class Person<T> {
name: T;
sayHi: (x: T) => `hi,${x}`
}
拿上面举的例子,我们想要获取传入值的长度,那我们可以通过约束类型的方式来实现
type Length {
length: number;
}
function log<T extends Length>(value: T):T {
return value
}
3. 类型推断
当我们给一个变量直接赋值的时候,比如let a = 1,那么a自动就被认为是number类型,这也算是一种推论;
我们也可以使用as:
interface Foo { bar: number }
let foo = { } as Foo;
foo.bar = 1;
但是使用as容易遗漏,除非非常确定的情况,最好的方式:
let foo:Foo = {
bar: 1;
}
4. 类型兼容
当一个类型Y可以被赋值给另一个类型X时,就可以说类型X兼容Y
X(目标类型) = Y(源类型)
4.1 接口兼容性
只要Y具备了X所有属性那么,X兼容Y,即X = Y,属性少的兼容属性多的
interface X {
a: any;
}
interface Y {
a: any;
b: any;
}
let x: X = { a: 1 };
let y: Y = { a: 1, b: 2 };
x = y; //成功
y = x; //失败
4.2 函数兼容性
①参数个数匹配
函数参数多的兼容参数少的,与上面接口的正好相反
type H = ({a: number, b: number}) => void
function h(handler: H) {
return handler;
}
-
传一个参数(成功)
h({a: 1} => {})
-
传两个参数(完全没问题)
h({a: 1, b: 2} => {})
-
传多个参数(大于固定的参数个数2)(失败了=.=)
h({a: 1, b: 2, c: 3} => {})
但是如果有可选参数和剩余参数的情况呢?情况入下图
let f1 = (p1: number, p2: number) => {}
let f2 = (p1?: number, p2?: number) => {}
let f3 = (...args: number[]) => {}
A/B (A兼容B) | 固定参数 | 可选参数 | 剩余参数 |
---|---|---|---|
固定参数f1 | f1=f2 √ | f1=f3 √ | |
可选参数f2 | × | × | |
剩余参数f3 | f3=f1 √ | f3=f2 √ |
对于可选参数,我们还可以设置"strictFunctionTypes": false,这样可选参数就可以都兼容固定参数和剩余参数了。
同理:
interface p1 { x:number, y: number }
interface p2 {x: number}
let f1 = (p: p1) => {}
let f2 = (p: p2) => {}
f1 = f2 // 成功
f2 = f1 //失败
也是参数多的兼容参数少的
②参数类型匹配
如果是string类型的参数肯定是无法与number类型参数匹配的
③返回值类型匹配
要求目标函数的返回值类型与源函数的一样,或者为其子类型。
即少的兼容多的
let f3 = () => ({ a: 1})
let f4 = () => ({ a: 1, b: 2})
f3 = f4 // 成功
f4 = f3 // 失败
4.3 枚举兼容性
枚举和number是相互兼容的,但是枚举与枚举之间是不相互兼容的
enum Fruit { Apple, Banana };
enum Color { Red };
let fruit: Fruit.Apple = 1
let index:number = Fruit.Apple
let color: Color.Red = Fruit.Apple // 失败
4.4类兼容性
对于类的兼容,静态成员和构造函数是不参与比较的,只比较结构。
class A {
id: number = 1;
constructor(p: number, q:number) {}
}
class B {
static name: string;
id: number = 2
constructor(p: number) {}
}
此时类A和B是相互兼容的,但是如果类中含有私有成员,就无法相互兼容,只有父类和子类的关系才会兼容。
4.5泛型兼容性
根据结构
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y // 成功
x和y可以相互兼容,但是如果接口Empty有具体的结构
interface NotEmpty<T> {
data: T;
}
let x: Empty<number>;
let y: Empty<string>;
x = y // 失败
当泛型被指定了具体的类型就不兼容了,但是如果这两个泛型函数定义相同,那么也是可以兼容的
总之:
结构之间兼容: 成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
5. 类型保护
定义:在特定的区域中保证变量属于某种确定的类型,在此区块中放心引用此类型的属性或调用方法
①instanceof
直接判断是否属于某个类型,举个例子:
if(a instanceof Java) {
Java.helloJava()
} else {
a.hellJs()
}
②in(判断某个睡醒是否属于某个变量)
if('name' in Person) { ... } else { ... }
③typeof
这个就是普通的类型判断
if(typeof x === 'string') {
x.length
} else { ... }
④is
function isString1(a: any):a is String {
return typeof a === "string";
}
funciton isString2(a: any): boolean {
return typeof a === "string";
}
function example(foo: any) {
if(isString1(foo)) {
console.log(foo.toFixed(1)) // 编译出错
}
if(isString2(foo)) {
console.log(foo.toFixed(1)) // 通过
}
console.log(f.toFixed(1)) // 通过
}
这边有两种写法,同样都是判断一个变量是否为String类型,区别在于使用is会进一步缩小变量的类型,而且类型保护的作用域仅仅在if后的块级作用域中生效。
6. ts高级类型
6.1交叉类型
将多个类型合并成一个类型,新类型拥有所有类型特性
interface Dog {
run():void
}
interface Cat {
jump(): void
}
let pet: Dog & Cat = { run(){}, jump(){} }
6.2联合类型
声明的类型不确定,可能为多个类型中的一个
let a: number | string = "a"
let b: 'a' | 'b' | 'c' = "b"
当一个变量是两个类型的联合类型时,取交集类型
联合类型的使用场景:用于同一个行为的类型
interface Rectangle { kind: 'R', width: number, height: number }
interface Square { kind: 'S', size: number }
interface Circle { kind: 'C', r: number }
type Shape = Rectangle | Square | Circle
function getArea(s: Shape) {
switch(s.kind) {
case 'R': {
return s.width * s.height
};
case 'S': {
return s.size * s.size
};
case 'C': {
return Math.PI * s.r ** 2
};
default: return ((e: never) => { throw new Error(e)})(s)
// 如果此处出现变异错误,说明类型考虑得不完全
}
}
6.3索引类型
let obj = {
a: 1,
b: 2
}
function getValue(obj: any, keys: string[]){
return keys.map(key => obj[key])
}
console.log(getValue(obj, ['a', 'b']))
// [1, 2]
console.log(getValue(obj, ['c']))
//undefined
当我们想要根据key值获取value时候,最后一种情况的undefined没有被检查出来,我们需要使用索引类型来约束:
let obj = {
a: 1,
b: 2
}
function getValue<T, K extends keyof T>(obj: T, keys: K[]){
return keys.map(key => obj[key])
}
console.log(getValue(obj, ['a', 'b']))
console.log(getValue(obj, ['c']))
这时候最后一种就可以被检查出来了,因为对第二参数进行了约束,即K必须是T的key值。
2.映射类型
①Readonly
当我们想要让一个接口的所有的属性都变成仅可读,那么:
interface Obj {
a: number,
b: string,
c: boolean
}
type readOnlyObj = Readonly<Obj>
内部实现的代码:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
解析:遍历类型T中的所有key值,将所有值变成readonly
②Partial
同样,让所有类型变成可选的方法:
type partialObj = Partial<Obj>
内部实现的代码:
type Partial<T> = {
[P in keyof T]?: T[P];
};
③Pick
抽取类型的,可以抽取出一个类型的子集
type PickObj = Pick<Obj, 'a' | 'b'>
内部代码:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
以上成为同态,不会产生新的类型,都在obj内
④Record
引入其他key值,使用obj作为其类型,称为不同态
type RecordObj = Record<'x' | 'y', Obj>
结果:
type RecordObj = {
x: Obj;
y: Obj;
}
⑤Omit
可以使用Pick来实现
Omit<T, U> = Pick<T, Exclude<keyof T, U>
第一步:Exclude<keyof T, U> 获取T中所有key除了U以外的所有key;
第二步:抽取T中 Exclude<keyof T, U>属性值组成的对象
type person = { name: string, age: number, address: string }
let a: Omit<person, "name"> = { age: 1, address: "fujian" }
// 等价:
Pick<person, Exclude<"name" | "age" | "address", "name">
= Pick<person, "age" | "address">
= {
age: number,
address: string
}
说白了,Omit就是取T对象类型中除了U属性组成的对象类型
3.条件类型
T extends U ? X : Y
解析:如果T可以被赋值给U,那么去类型X,否则取类型Y
type getName<T> = T extends string ? "string" : "other"
getName<string> // "string"
联合类型的条件类型:
(A | B) extends U ? X : Y
//可以拆分成
(A extends U? X : Y) | (B extends U? X : Y)
getName<string | number>
// 结果为: "String" | "other"
应用场景:
(1)可以筛选出无法被赋值到另一个类型的属性
type Diff<T, U> = T extends U ? never : T
type t = Diff<"a" | "b" | "c", "a" | "e">
// 结果为 "b" | "c"
解析:
Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
= never | "b" | "c"
= "b" | "c"
(2)可以过滤类型
type NotNull<T> = Diff<T, undefined | null>
type t = NotNull<string | number | undefined | null>
// 结果为 string | number
内置类型
①Exclude
Exclude<T, U>
type a = Exclude<"a" | "b", "a">
// 结果 "b"
抽取T中无法赋值给U的类型
②NonNullable
type b = NonNullable<"a" | "b" | null | undefined>
筛选掉null, undefined
③Extract
type c = Extract<"a" | "b", "a">
抽取T中可以赋值给U的类型,和Exclude相反
④ReturnType
获取参数返回值类型
type a = ReturnType<() => string>
//结果为 sring
内部代码:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
infer待推断