TypeScript学习笔记
目录
前言:
不同于JS的弱类型语言,TS在JS基础上增加了几个新类型,并约束了变量的类型,不同的类型间一般无法相互赋值,以 变量:类型 的形式为一个变量指定类型,
1 TS基础知识
1.1 TS中新增六个类型
(1)元组
元组其实是知道长度和每个元素类型的数组,需要个数和类型与变量后规定的类型一一对应
let Tuple: [String, Number] // Tuple只是变量名可以改变
Tuple = ['z', 1]
Tuple = ['z', '1'] // error 不能将类型“string”分配给类型“Number”
(2)枚举
使用enum 关键字声明
enum demo {
one,
two,
}
// 相当于:
let obj = {
0: 'one',
1: "two",
one: 1,
two: 2
}
注:
- 访问属性的方式和对象类似
- 可以通过索引访问,也可以通过属性名访问
- 通过属性名 = number的形式指定枚举的索引,并且后续的枚举不指定值得情况下,从指定值之上开始累加
- 枚举赋值也可以赋值字符串
- const关键字定义的enum枚举对象,不会在js中进行编译,使用的时候只会将其中的值取出来使用
- 反向映射只支持数字枚举,字符串枚举是不支持的
(3)any
表示任意类型,给一个变量指定any后,就可给他赋值不同的类型变量
(4)void
表示没有类型,和any相反,当一个函数不设定返回值时,可以指定它的返回值类型为void
void 类型的变量只能赋值为 undefined
和 null
,其他类型不能赋值给 void 类型的变量
(5)never
值永不存在值得类型。它是那些总会抛出异常或根本不会有返回值的函数(不同于不设定返回值默认为undefined,当一个函数死循环或者异常时可以指定never)表达式的返回值类型,当变量被永不为真的类型保护(后面章节会详细介绍)所约束时,该变量也是 never 类型
(6)unknown
表示未知类型,多使用unknown替代any,unknown更安全
1.2 类型断言:
当不确定typescript联合类型(变量属于多种类型中之一)的变量到底是哪一种的时候,需要使用到类型断言
语法为:<type>value 或者 value as type(推荐)
2 接口interface可以看作一个复杂的类型
2.1 定义接口
interface Info {
firstName: string;
lastName: string;
}
const getFullName = ({ firstName, lastName } : Info) => return `${firstName} ${lastName}`
2.2 属性规则
(1)可选属性
interface Info {
firstName: string;
lastName?: string;
}
const getFullName = ({ firstName, lastName } : Info) => {
return `${firstName} ${lastName ? lastName : ""}`
}
(2)多余属性检查
interface Info {
firstName: string;
lastName?: string;
}
const getFullName = ({ firstName, lastName } : Info) => {
return `${firstName} ${lastName ? lastName : ""} ${size}`
// error: Property 'size' does not exist on type 'Info'
}
绕开多余属性的检查:
- 使用类型断言
interface Info {
firstName: string;
lastName?: string;
}
const getFullName = ({ firstName, lastName, age } : Info) => {
return `${firstName} ${lastName ? lastName : ""} ${age}`
}
getFullName({
firstName: 'li',
lastName: 'ming',
age: 12
} as Info)
- 添加索引签名
interface Info {
firstName: string;
lastName?: string;
[prop: string]: any;
}
const getFullName = ({ firstName, lastName, age } : Info) => {
return `${firstName} ${lastName ? lastName : ""} ${age}`
}
getFullName({
firstName: 'li',
lastName: 'ming',
age: 12
})
- 利用类型兼容
interface Info {
firstName: string;
lastName?: string;
}
const getFullName = ({ firstName, lastName, age } : Info) => {
return `${firstName} ${lastName ? lastName : ""} ${age}`
}
const object = {
firstName: 'li',
lastName: 'ming',
age: 12
}
getFullName(object)
(3)只读属性
interface Info {
readonly name: string;
age: number;
}
const student: Info = {
name: 'Li Ming',
age: 12
}
student.name = 'Zhang San' // Cannot assign to 'name' because it is a read-only property
student.age = 18
注:const和readonly的选择, 如果是定义一个常量,那用const
,如果这个值是作为对象的属性,那请用readonly,且const声明的变量对象其中的属性是可以修改的
(4)函数类型
// (a: type, b: type): type形式,其中()内为参数类型, :之后为函数返回值类型
interface func {
(name: string, age: number): string
}
const boy: func = (name, age) => {
return `${name} ${age}`
}
2.3 高级用法
(1)索引类型
interface indexType {
[id: number]: string;
}
const index: indexType = {
0: "one",
1: "two"
}
const index2: indexType = {
m: "one",
// error 不能将类型"{ s: string; a: string; }"分配给类型"RoleDic"
}
(2)继承接口
interface color {
color: string;
}
interface book extends color {
name: string
}
interface demo extends book {
age: string
}
// interface demo {
// color: string;
// name: string;
// age: string
// }
const people: demo = {
color: 'red',
name: 'zs',
age: '12'
}
(3)混合类型的接口
interface Sum {
(): number;
num1: number;
num2: number;
}
const getSum = (): Sum => {
const func = () => {
return func.num1 + func.num2 // 此处的返回值通过第一项:number定义
}
func.num1 = 5
func.num2 = 10
return func
}
const sum = getSum()
混合类型的后几项为, 定义的函数对象里的属性规则
3 函数
3.1 函数类型
(1)简单定义
function demo(arg1: number, arg2: number): string {
return arg1 + arg2 + ''
}
(2)完整定义
// 此处相当于变量的声明,规定此变量为函数类型
let demo: (arg1: number, arg2: number) => number
demo = (num1: number, num2: number): number => num1 + num2
(3)使用接口定义
interface demoIf {
(arg1: number, arg2: number): number
}
let add: demoIf = (num1: number, num2: number): number => num1 + num2
(4)使用类型别名
type demo = (arg1: number, arg2: number) => number
let add: demo = (num1: number, num2: number): number => num1 + num2
3.2 参数
(1)可选参数
type demo = (arg1: number, arg2: number, arg3?: number) => number
let add: demo = (num1: number, num2: number, num3: any): any => {
if (num3) {
return num1 + num2 + num3
} else {
return num1 + num2
}
}
console.log(add(1, 2)) // 3
console.log(add(1, 2, 3)) // 6
// 必选参数需要在可选参数的前面
(2)默认参数
function add (num1: number, num2: number = 2): number {
return num1 + num2
}
(3)剩余参数
const handleData = (arg1: number, ...args: number[]) => {};
3.3 函数重载
function fun (x: string): number
function fun (x: number): string
function fun (x: any): any {
if (typeof x === 'string') {
return x.length
} else {
return x.toString()
}
}
console.log(fun(2)) // '2'
console.log(fun("1")) // 1
console.log(fun([1])) // error
注:重载只能用 function 来定义,不能使用接口、类型别名
4 泛型函数
4.1 基本语法
// 需要使用function
function demo<G>(str1: G): G {
return str1
}
console.log(demo<string>('s')) // "s"
4.2 利用<type>定义泛型类的函数和函数类的泛型
(1)定义泛型类型的函数
type FunctionType<TValue> =(a: TValue) => TValue;
const demo: FunctionType<string> = (x: string): string => {
return x
}
(2)定义泛型函数的类型(函数更为零活,参数多变)
type FunctionType = <TValue>(a: TValue) => TValue;
const demo: FunctionType = <TValue>(x: TValue): TValue => {
return x
}
<>里参数为函数中各个参数的类型。如<A, B> 即为函数中参数或者返回值需要用到的类型
除了上述的定义方式,也可以使用interface或者函数直接定义使用
4.3 泛型约束
interface valueWithJoin {
join: Function
}
// TValue 继承接口valueWithJoin,代表实参必须有join这个属性且为Function类型才可以
type FunctionType = <TValue extends valueWithJoin>(a: TValue, b: string) => string[];
const demo: FunctionType = <TValue extends valueWithJoin>(x: TValue, y: string): string[] => {
return (x.join("") + y).split("")
}
console.log(demo([1, 2], 'a')) // ["1", "2", "a"]
4.4 在泛型约束中使用类型参数
type getPropType = <A, B extends keyof A>(obj: A, index: B) => A[B]
const getProp: getPropType = <A, B extends keyof A>(obj: A, i: B) => obj[i]
console.log(getProp({num: 1, age: 20}, "num")) // 1
console.log(getProp({num: 1, age: 20}, "age")) // 20
console.log(getProp({num: 1, age: 20}, "name")) // error 类型“"name"”的参数不能赋给类型“"num" | "age"”的参数。
5 TS中的类
5.1 基本语法
class People {
name: string;
age: number;
constructor (name: string, age: number) {
this.name = name;
this.age = age;
}
}
const zs = new People('zs', 20)
继承的使用和js基本相似
5.2 修饰符
(1)public
public表示公共的,用来指定在创建实例后可以通过实例访问的,也就是类定义的外部可以访问的属性和方法。默认是 public,但是 TSLint 可能会要求你必须用修饰符来表明这个属性或方法是什么类型的。
class People {
public name: string;
public age: number;
constructor (name: string, age: number) {
this.name = name;
this.age = age;
}
public getName() {
return this.name
}
}
const zs = new People('zs', 20)
console.log(zs.getName()) // "zs"
(2)private
此修饰符表示私有的,它修饰的属性在类的定义外面是没法访问的
class People {
public name: string;
public age: number;
constructor (name: string, age: number) {
this.name = name;
this.age = age;
}
public getName() {
return this.name
}
private getAge() {
return this.age
}
}
const zs = new People('zs', 20)
console.log(zs.getName()) // "zs"
console.log(zs.getAge()) // error 属性“getAge”为私有属性,只能在类“People”中访问
注:在子类的继承中也是无法使用父类的私有属性或方法
(3)protected
表示被保护的属性或方法,在实例中仍无法访问,但是不同于private,他可以在子类中访问
super关键字可以访问的是受保护的方法和公共方法,所以会产生报错
(4)readonly
设置属性为只读,无法修改
5.3 类里的属性
(1)参数属性
在constructor的参数中将修饰符放在前面可以简化类的书写
class Demo {
constructor (public name: string) {
this.name = name
// 等同于在constructor前用public先声明
}
}
const obj = new Demo('zs')
(2)静态属性
在属性或方法前加static属性,创建实例的时候,不会生成此属性或者方法
class Demo {
constructor (public age: string) {
this.age = age
}
public static getNumber() {
return 123
}
}
const obj = new Demo('zs')
console.log(obj) // { name: 'zs' }
console.log(Demo.getNumber()) // 123
只支持写在constructor外的属性和方法
(3)可选类属性
使用?占位,表示可以不传入这个属性或方法
class Demo {
constructor (public age: number, public name?: string) {
this.age = age
this.name = name
}
public static getNumber() {
return 123
}
}
const obj = new Demo(12)
console.log(obj) // { name: 12, name: undefined }
const obj2 = new Demo(12, 'zs')
console.log(obj2) // { age: 12, name: "zs" }
(4)存取器
class getSetValue {
private __fullName: string = 'zs'
get fullName() {
return this.__fullName
}
set fullName(value: string) {
console.log('set')
this.__fullName = value
}
}
const lisi = new getSetValue()
lisi.fullName = 'lisi'
console.log(lisi.fullName)
注:get和set的命名需要一致,他们相当于两个公共的方法,在设置值时调用set对应的fullName()方法给__fullName(与get和set的命名无关,可以是任何名字)赋一个值,在获取这个值时调用get对应的fullName获取这个值
(5)抽象类
一般用来被其余类继承的类,称为抽象类,不能直接用来创建实例对象,使用abstract关键字定义
abstract class Demo {
constructor (public age: number) {
this.age = age
}
abstract returnAge(): number
}
class Son extends Demo {
constructor (age: number) {
super(age)
this.age = age
}
returnAge() {
return this.age
}
}
const obj = new Son(18)
console.log(obj.returnAge()) // 18
抽象类里定义的抽象方法,需要继承类中先定义
抽象方法和抽象存取器不能包含实际代码块
注:实例的类型即是class声明的构造函数
(6)类类型接口
interface missProp {
name: string;
age: number;
}
class People implements missProp { // error 类型 "People" 中缺少属性 "age",但类型 "missProp" 中需要该属性。
constructor(public name: string) {
this.name = name
}
}
通过implements关键字让类的声明中必须包含接口规定的字段
注:有一点需要注意,接口检测的是使用该接口定义的类创建的实例,所以通过static定义的静态属性依旧会报错
6 类型
6.1 类型推论
6.1.1 基础使用
let na = 'zs'
na = 123 // error 不能将类型“number”分配给类型“string”
直接将一个值赋值给一个变量,而不去声明他的类型,ts会自动推断出它的的类型,在后续赋值操作时,应保持和一开始赋的值的类型相同
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,所以现在加了对参数target
和返回值的类型定义之后就会报错
(1)多类型联合
在定义一个数组或元组这种拥有多种数据类型的值得时候,ts会将其中的各项元素的类型进行联合
let tuple = [1, "q"]
tuple = [false] // error 不能将类型“boolean”分配给类型“string | number”
let value = Math.random() * 10 > 5 ? 'abc' : 123
value = false // error 不能将类型“false”分配给类型“string | number”
(2)上下文类型
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.a); // error 类型“MouseEvent”上不存在属性“a”
};
表达式左侧是 window.onmousedown(鼠标按下时发生事件),因此 TypeScript 会推断赋值表达式右侧函数的参数是事件对象,因为左侧是 mousedown 事件,所以 TypeScript 推断 mouseEvent 的类型是鼠标按下事件。在回调函数中使用 mouseEvent 的时候,你可以访问鼠标事件对象的所有属性和方法,当访问不存在属性的时候,就会报错。
6.2 类型兼容性
6.2.1 函数兼容性
(1)参数匹配
如果x要赋值给y需要x的参数小于或等于y的参数个数,并且类型符合y参数类型,参数名无所谓
let x = (a: number) => a.toString()
let y = (a: number, b: string) => a + b
y = x
x = y // error 不能将类型“(a: number, b: string) => string”分配给类型“(a: number) => string”
(2)剩余参数和可选参数
let demo = (arg1: number, ...args: number[]): number[] => {
return new Array(arg1, ...args)
}
console.log(demo(1, 2, 3)) // [1, 2, 3]
剩余参数与可选参数类似,剩余参数可以看做和很多可选参数组合而成,传入剩余参数时,可以是0个也可以是很多个
(3)函数参数的双向协变
let demo = function (a: number | string): void {}
let demo2 = function (b: number): void {}
demo2 = demo
注:报错是tsconfig.json文件的"strictFunctionTypes"选项为false,默认为false,但是如果你设置了"strict"为true,需要显式设置"strictFunctionTypes"为false。
(4)函数返回值类型
和参数类型相似,函数的返回值类型也需要匹配才可以相互赋值
(5)函数重载
赋值的函数重载,需要在被赋值的函数重载中的每一条都包括
function merge(arg1: number, arg2: number): number; // 这是merge函数重载的一部分
function merge(arg1: string, arg2: string): string; // 这也是merge函数重载的一部分
function merge(arg1: any, arg2: any) { // 这是merge函数实体
return arg1 + arg2;
}
function sum(arg1: number, arg2: number): number; // 这是sum函数重载的一部分
function sum(arg1: any, arg2: any): any { // 这是sum函数实体
return arg1 + arg2;
}
let func = merge;
func = sum; // error 不能将类型“(arg1: number, arg2: number) => number”分配给类型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”
6.3 枚举兼容性
enum demo {
one,
two
}
let num = demo.two
num = 10
enum numb {
one,
two
}
let test = demo.one
test = numb.one // error 不能将类型“numb.one”分配给类型“demo”
枚举与数字类型兼容;
枚举与枚举不兼容;
枚举与字符串不兼容;
6.4 类的兼容
两个类的兼容,只看他们的实例对象,静态成员和公共方法不考虑
(1)基本
class People {
static age: number;
constructor (public name: string) {
this.name = name
}
}
class Dog {
static master: string;
constructor (public name: string) {
this.name = name
}
}
let zs: People = new People('zs')
let ahua: Dog = new Dog('ahua')
zs = ahua
console.log(zs) // Dog { name: 'ahua' }
(2)私有成员和受保护的成员
class People {
private age: number = 12;
constructor (public name: string) {
this.name = name
}
}
class son extends People {
constructor(name: string) {
super(name)
this.name = name
}
}
class Dog {
private age: number = 55;
constructor (public name: string) {
this.name = name
}
}
let ls: People = new son('ls')
let zs: People = new Dog('zs') // error 不能将类型“Dog”分配给类型“People”。类型具有私有属性“age”的单独声明。
对于拥有私有成员或受保护成员的类,他只兼容自身的子类(继承自它)
6.5 泛型兼容性
interface Value<V> {}
let value1: Value<string> = 1;
let value2: Value<number>;
value2 = value1
interface ValueNext<T> {
s: T
}
let value3: ValueNext<string> = { s: "a" };
let value4: ValueNext<number>;
value4 = value3 // error 不能将类型“ValueNext<string>”分配给类型“ValueNext<number>
ts是结构类型的语法,接口定义泛型,未使用的情况下,不做类型限制,所以两个变量彼此兼容,使用后,会对使用该接口的对象的属性进行类型限制,所以不兼容
7 类型保护
与类型断言应用场景相似,类型保护相当于自己声明一个可以判断值得类型的函数
(1)自定义类型保护
function isString(value: string | number): value is string {
// value is type(value是参数名,type是需要判断的类型)
return typeof value === "string"
}
function getValue() {
const random = Math.random() * 10
if (random > 5) {
return 'string'
} else {
return 1
}
}
const item = getValue()
// if (item.length) { 类型“"string" | 1”上不存在属性“length”
// console.log(item.length)
// } else {
// console.log(item.toFixed())
// }
if (isString(item)) {
console.log(item.length)
} else {
console.log(item.toFixed())
}
(3)typeof 类型保护
可以将上述的自定义函数,替换成typeof来进行判断,也可以起到同样的作用
if (typeof item === "string") {}
ts中typeof的书写规范
-
只能使用等于和不等两种形式来比较
-
type 只能是
number
、string
、boolean
和symbol
四种类型
(4)instanceof 类型保护
function typeConstructor() {
const random = Math.random() * 10
if (random > 5) {
return ['a']
} else {
return { name: 'zs' }
}
}
const value = typeConstructor()
// 数组的构造函数也有Object,但对象的构造没有Array,所以需要先判断Array
if (value instanceof Array) {
console.log(value[0])
} else {
console.log(value.name)
}
8 显示复制断言
8.1 null和undefined的补充
(1)严格模式的null和undefined赋值
当我们在严格模式(tsconfig.js配置strictNullChecks为true)下,就不能将null和undefined赋值给除自身和void之外的类型,但是一个对象有时需要先设置为null或者undefined,可以使用以下方式解决:
let obj: null | object = { name: 'zs' }
obj = null
obj = {}
(2)可选参数和可选属性
开启了 strictNullChecks 后,可选参数会被加上 | undefined
interface demo {
name: string
age?: number
}
const obj: demo = {
name: 'zs',
age: 12
}
obj.age = undefined // 不报错(property) demo.age?: number | undefined
8.2 显示赋值断言
function judgeNull(num: number | null): string {
// return num.toString() // error 对象可能为 "null"
return num!.toString()
}
在可能为null的值后面加!,表示此值是不为null的情况
9 类型别名和字面量类型
(1)类型别名
type typeString = string
type demoType = { a: string, c: object }
type demoType1<T> = { a: T }
type demoType2 = { a: string, c?: demoType }
const demoObj: demoType = {
a: 'zs',
c: {
a: 'ls',
c: {
a: 'zs',
}
}
}
注:
- 类型别名可以是一个具体的例子,如demoType
- 类型别名可以在别名中引用自身(只在对象中有用)
- 类型别名只是为其它类型起了个新名字来引用这个类型,所以当它为接口起别名时,不能使用
extends
和implements
(2)字面量类型
- 字符串字面量(具体的值,而不是字符串类型)
type name = "zs"
let student1: name = 'ls' // error 不能将类型“"ls"”分配给类型“"zs"”
- 数字字面量(与字符串字面量类似)
10 可辨识联合类型
(1)要素
-
具有普通的单例类型属性(这个要作为辨识的特征,也是重要因素)
-
一个类型别名,包含了那些类型的联合(即把几个类型封装为联合类型,并起一个别名)
-
case语句匹配结果
type zs = {
name: 'zs',
hobby: string,
}
type ls = {
name: 'ls',
hobby: string,
}
type zw = {
name: 'zw',
hobby: string,
}
type student = zs | ls | zw
function getStudentHobby(student: student): string {
switch (student.name) {
case 'zs':
return student.hobby = 'soccer';
case 'ls':
return student.hobby = 'read'
case 'zw':
return student.hobby = 'play'
}
}
(2)完整性检查
- 开启 strictNullChecks,然后让函数的返回值类型为指定类型,此时少写一个case会报错
11 TS中的this
ts中的this是一个类型,对于函数而言,this可以理解为对象的字面量类型或者函数的构造函数构造的一个和实例相同的类型,具体的使用其实和js的this相似,但是ts中可以更改this的类型为别的类型
interface funRules<N, T> {
info: T;
play: N & ThisType<N & T>;
}
function demo<N, T>(obj: funRules<N, T>): N & T {
let info = obj.info || {}
let play = obj.play || {}
return { ...info, ...play } as N & T
}
let demoPro = demo({
info: { x: 0 },
play: {
// 没有 & <N & T> 报错:类型“{ add(a: number): any; }”上不存在属性“x”
add(a: number): any {
return this.x += a
}
}
})
console.log(demoPro.add(5)) // 5
补充:ThisType是一个内置的接口,用来在对象字面量中键入this,指定当前属性的this的类型
12 索引类型
12.1 索引类型查询字符串
keyof操作符,连接一个类型的时候,会返回一个由这个类型所有属性名组成的联合类型
interface People {
name: string;
age: number;
}
let newPeople: keyof People // 相当于 "name" | "age"
newPeople = 'name'
newPeople = 'age'
newPeople = 'a' // error 不能将类型“"a"”分配给类型“keyof People”
动态监测属性名的实现
function getValue<T, K extends keyof T>(obj: T, attr: K): T[K] {
return obj[attr]
}
const info = {
name: 'zs',
age: 18
}
let res = getValue(info, 'name') // 'zs'
let res2 = getValue(info, 'age') // 18
let res3 = getValue(info, 'play') // error 类型“"play"”的参数不能赋给类型“"name" | "age"”的参数
12.2 索引访问操作符
使用语法和访问数组和对象的属性一样,使用 []
interface demo {
name: 'zs'
}
type Name = demo['name']
let zs: Name = 18 // error 不能将类型“18”分配给类型“"zs"”。
interface demo {
[name: string]: string
}
let Names: keyof demo = true // error 不能将类型“boolean”分配给类型“string | number”
如果接口的索引类型是 string 类型,那么实现该接口的对象的属性名设置为数值类型的值也是可以的,因为数值最后还是会先转换为字符串。这里一样,如果接口的索引类型设置为 string 的话,keyof Obj<number>
等同于类型number | string
当tsconfig.json里strictNullChecks
设为false
时,通过Type[keyof Type]
获取到的,是除去never & undefined & null
这三个类型之后的字段值类型组成的联合类型
13 映射类型
13.1 基础
使用keyof借助别的类型,映射一个新类型
interface demo {
name: string
}
type newDemo<T> = { readonly [P in keyof T]: T[P] }
type newDemoType = newDemo<demo>
let Name: newDemoType = { name: 'zs' }
Name.name = 'ls' // error 无法分配到 "name" ,因为它是只读属性
注:in在这里的作用相当于js中for...in....,可以将映射类型的属性名遍历并传入新的类型
(2)内置的映射类型:
// type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// type Record<K extends keyof any, T> = { [P in K]: T };
// type Partial<T> = { [P in keyof T]?: T[P] };
// pick:
interface demo {
name: string;
age: number;
hobby: string
}
const zs: demo = {
name: 'zs',
age: 18,
hobby: 'soccer'
}
function copyObj<T, K extends keyof T>(obj: T, newObj: K[]): Pick<T, K> {
let res = {} as Pick<T, K>
newObj.forEach(item => res[item] = obj[item])
return res
}
console.log(copyObj(zs, ['name', 'age'])) // {name: "zs", age: 18}
// Record:
interface reType {
one: string,
two: number,
three: boolean
}
const reObj: reType = {
one: 'one',
two: 2,
three: true
}
function recordFun<K extends keyof any, T>(obj: Record<K, T>, tran: (value: any) => T): Record<K, T> {
let res = {} as Record<K, T>
for (let key in obj) {
res[key] = tran(key)
}
return res
}
console.log(recordFun(reObj, a => a)) // {one: "one", two: "two", three: "three"}
同态:两个相同类型的代数结构之间的结构保持映射,Readonly、Partial 和 Pick 是同态的,而 Record 不是,因为 Record 映射出的对象属性值是新的,和输入的值的属性值不同
13.2 由映射类型进行推断
对一个对象由映射类型进行包装后,也可以逆向操作,俗称拆包
interface proxy<O> {
get(): O;
set(value: O): void
}
// 定义映射类型
type proxyType<T> = { [key in keyof T]: proxy<T[key]> }
// 定义处理函数
function proxyFun<T>(obj: T): proxyType<T> {
let res = {} as proxyType<T>
for (let key in obj) {
res[key] = {
get: () => obj[key],
set: (value) => {
obj[key] = value
}
}
}
return res
}
let obj = {
name: 'zs',
age: 18
}
let proxyObj = proxyFun(obj)
console.log(proxyObj.name.get()) // zs
proxyObj.name.set('ls')
console.log(proxyObj.name.get()) // ls
// 拆包函数
function unproxyFun<T>(obj: proxyType<T>): T {
let res = {} as T
for (let key in obj) {
res[key] = obj[key].get()
}
return res
}
let unproxyObj = unproxyFun(proxyObj)
console.log(unproxyObj) // {name: "ls", age: 18}
- 增加或者移除特定修饰符
映射类型中使用+
和-
符号作为前缀来指定增加还是删除修饰符
- keyof和映射类型在2.9中的升级
keyof 和映射类型支持用 number 和 symbol 命名的属性
- 元组和数组上的映射类型
TS 在 3.1 版本中,在元组和数组上的映射类型会生成新的元组和数组,并不会创建一个新的类型,这个类型上会具有 push、pop 等数组方法和数组属性
14 声明合并
声明合并是指ts会将相同名称的声明进行合并,合并后取并集
14.1 基础使用
interface demo {
name: string
}
interface demo {
age: number
}
let obj: demo = {
name: 'zs',
age: 18
} // interface demo = { name: string; age: number }
14.2 补充知识
TypeScript的所有声明概括起来,会创建这三种实体之一:命名空间、类型和值:
-
命名空间的创建实际是创建一个对象,对象的属性是在命名空间里export导出的内容;
-
类型的声明是创建一个类型并赋给一个名字;
-
值的声明就是创建一个在JavaScript中可以使用的值。
声明类型 |
创建了命名空间 |
创建了类型 |
创建了值 |
---|---|---|---|
Namespace |
√ |
√ |
|
Class |
√ |
√ |
|
Enum |
√ |
√ |
|
Interface |
√ |
||
Type Alias类型别名 |
√ |
||
Function |
√ |
||
Variable(变量) |
√ |
14.3 合并接口
(1)多个相同命名的接口的属性应该不同,且属性名相同,属性值不同时会报错
(2)对于函数成员,每个同名函数成员都会被当成这个函数的重载,且合并时后面的接口具有更高的优先级
14.4 合并命名空间
namespace demo {
export const name = () => {}
}
namespace demo {
export const age = () => {}
}
// namespace demo {
// export const name = () => {}
// export const age = () => {}
// }
注: 合并的只是导出的内容,对于没有导出的同名命名空间是无法访问其属性的
14.5 不同类型合并
(1)命名空间和类
类的定义需要在命名空间之前
class Demo {
add() {}
}
namespace Demo {
export const age = 18
}
console.log(Demo.prototype); // {add: ƒ, constructor: ƒ}
console.dir(Demo.prototype.constructor); // { age: 18 }
(2)命名空间和函数
函数的定义需要在命名空间之前
(3)命名空间和枚举
顺序前后没有要求,但合并的只是属性和值,不自动生成映射
15 条件类型
15.1 基础使用
和三元表达式类似,T是继承自string吗?是就继承string,不是就继承number
type demo<T> = T extends string? string: number
let a: demo<'a'>
let b: demo<1>
15.2 分布式条件类型
当待检测的类型是联合类型,则该条件类型被称为“分布式条件类型”
type TypeName<T> = T extends string
? string
: T extends number
? number
: T extends boolean
? boolean
: T extends undefined
? undefined
: T extends Function
? Function
: object;
type Type1 = TypeName<() => void>; // Type1的类型是Function
type Type2 = TypeName<string[]>; // Type2的类型是object
type Type3 = TypeName<(() => void) | string[]>; // Type3的类型是object | Function
类似上述的例子,Type1的逻辑为,() => void 是继承自string吗?不是则继承自number吗?不是则继承自boolean吗?以此类推,Type1的类型就是Function
type Diff<T, U> = T extends U ? never : T;
type Test = Diff<string | number | boolean, undefined | number>;
// Test的类型为string | boolean
上述例子,“string | number | boolean” 中number继承自“undefined | number”所以为never,剩下两个未继承则为string | boolean
15.3 条件类型的类型推断-infer
type demo<T> = T extends any[]? T[number]: T
// T[number]表示索引访问类型获取数组元素类型
// 此种写法可以替换为:
type demo2<T> = T extends Array<infer U> ? U : T;
// infer用来推断数组元素的类型为U
15.4 TS预定义条件类型
// Exclude<T, U>:从 T 中去掉属于 U 的类型
type exclude<T, U> = Exclude<T, U>
let demo1: exclude<string | number, number> = 1 // 不能将类型“number”分配给类型“string”
let demo2: exclude<string | number, number> = "1"
// Extract<T, U>,选取 T 中属于 U 的类型
type extract = Extract<string | number, number> // number
// NonNullable,从 T 中去掉 null 和 undefined
type nonNullable = NonNullable<string | number | undefined | null>; // string | number
// ReturnType,获取函数类型返回值类型
type returnType = ReturnType<() => string> // string
// InstanceType,获取构造函数类型的实例类型
// type InstanceType<T extends new (...args: any[]) => any> = T extends abstract new (...args: any[]) => infer R? R: any;
// abstract表示定义抽象类,抽象类的作用,作为类的模板,规范了其子类定义的标准,让代码更加规范
class demo {
constructor(public Name: string) {
this.Name = Name
}
}
type instanceType = InstanceType<typeof demo>
// demo是构造函数不是类型,所以要将构造函数转为构造函数的类型,然后推断构造函数类型的实例类型
16 入手装饰器
16.1 基础
注:装饰器仍然是一项实验性特性,未来可能有所改变,所以如果你要使用装饰器,需要在 tsconfig.json 的编译配置中开启experimentalDecorators
,将它设为 true
(1)定义
装饰器是一种新的声明,它能够作用于类声明、方法、访问符、属性和参数上。使用@
符号加一个名字来定义,名字需要是一个函数,或者求值后是一个函数,这个函数在运行的时候被调用,被装饰的声明作为参数会自动传入
装饰器要紧跟在要修饰的内容前面
而且所有的装饰器不能用在声明文件(.d.ts)中,和任何外部上下文中(比如 declare,关于.d.ts 和 declare,我们都会在讲声明文件一课时学习)
function setProp (target) {
// ...
}
@setProp
函数中的参数表示要修饰的目标
(2)装饰器工厂
也是一个函数,且返回值仍是一个函数,返回的函数作为装饰器的调用函数,如果使用装饰器工厂,在定义修饰器的时候后面加()
function setProp () {
return function (target) {
// ...
}
}
@setProp()
(3)装饰器的组合
写多个装饰器,写法可以同一行定义,使用空格隔开,也可以换行 @xx @xxx
注意:
-
装饰器工厂从上到下依次执行,但是只是用于返回函数但不调用函数;
-
装饰器函数从下到上依次执行,也就是执行工厂函数返回的函数,调用函数。
function demo1() {
console.log(1)
return function (target) {
console.log(4);
}
}
function demo2() {
console.log(2);
return function (target) {
console.log(3);
}
}
@demo1()
@demo2()
class Test {} // 1 2 3 4
(4)装饰器顺序
类的定义中不同声明上的装饰器将按以下规定的顺序引用:
-
参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员;
-
参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员;
-
参数装饰器应用到构造函数;
-
类装饰器应用到类。
16.2 装饰器类型
(1)类装饰器
let sign = null
function demo1() {
console.log(1)
return function (target: Function) {
sign = target
console.log(target.name);
}
}
@demo1() // Test
class Test {}
console.log(sign === Test); // true
装饰器函数的target其实就是你要修饰的目标本身,对于上面这个例子,也就是说target(全局变量sign被赋值为这个target)代表的是要修饰的Test这个类,所以在控制台打印这个结果为true
function demo1() {
return function <T extends { new (...args: any[]): {} }>(target: T) {
target.prototype.age = 18
return class extends target {
a = 'a';
b = 'b'
}
}
}
@demo1() // Test
class Test {
property = "property";
a: string
constructor (arg: string) {
this.a = arg
}
}
interface Test {
age: number
}
// 通过装饰器,可以修改类的原型对象和构造函数
console.log(new Test('s')); // {property: "property", a: "a", b: "b"}
通过return返回值得方式,可以对实例对象属性的修改和覆盖,且以返回属性为主
(2)方法装饰器
方法装饰器用来处理类中方法,它可以处理方法的属性描述符,可以处理方法定义。方法装饰器在运行时也是被当做函数调用,含 3 个参数:
-
装饰静态成员时是类的构造函数,装饰实例成员时是类的原型对象;
-
成员的名字;
-
成员的属性描述符(defineProperty)。
function demo(
target: any,
propertyName: string,
propertyDescriptor: PropertyDescriptor
) {
console.log(target);
propertyDescriptor.enumerable = true
}
class Demo {
_name: string = 'zs'
@demo
getName() {
return this._name
}
}
let obj = new Demo()
console.log(obj); // { _name: "zs" }
for (let key in obj) {
console.log(obj[key]);
}
// true: zs ƒ () { return this._name }
// false: zs
// 如果方法装饰器返回一个值,那么会用这个值作为方法的属性描述符对象
// 也就是propertyDescriptor.enumerable = true ==》 return { enumerable = true }
(3)访问器装饰器
类中的get和set也就是访问器,参数也是三个,且与方法装饰器相似,用法也类似,不过操作的是get和set对应的那个属性的属性描述符
(4)属性装饰器
有两个参数,构造函数和成员名
只能判断某个类中是否声明了此名字的属性
(5)参数装饰器
参数装饰器有 3 个参数,前两个和方法装饰器的前两个参数一模一样:
-
装饰静态成员时是类的构造函数,装饰实例成员时是类的原型对象;
-
成员的名字;
-
参数在函数参数列表中的索引。
写在需要装饰的方法定义的()的参数之前,且作为函数调用
function required(target: any, propertName: string, index: number) {
console.log(`修饰的是${propertName}的第${index + 1}个参数`);
}
class Info {
getInfo(prefix: string, @required infoType: string): any {
return prefix + " " + this[infoType];
}
}
const info = new Info();
info.getInfo("hihi", "age"); // 修饰的是getInfo的第2个参数