typescript简介
typescript的功能是静态类型校验,校验时间在代码编写阶段。
安装: npm install typescript -g 或者 yarn add typescript -g
查看版本号: tsc -v
本地测试
初始化ts项目:tsc -init
新建01.ts
初体验:
ts会根据初始赋值来确定变量的类型,并且str之后只能是string类型。
ts编译成js:tsc 文件名
编译出的js文件在根目录,建议分开:
找到tsconfig.json文件,全局搜outDir,它的作用是:ts编译成js后,将js文件放在哪,./是在当前目录下
将outDir解除注释,改成outDir:"./js/",将编译后的js文件放在js文件夹下,现在去创建js文件夹&修改outDir:
删除01.js,并在命令行执行:tsc -p tsconfig.json --watch
该命令意思是:实时监测,ts文件改完后保存,会根据outDir指定的目录,生成对应的js文件。
ts原始类型
变量p1的类型取决于初始赋值给它的值是什么类型,在这里是string
const声明常量,常量不能修改,所以p2的类型就是它的值
6
ts原始类型有哪些?
js基础数据类型:number,string,boolean,undefined,null,symbol
ts原始类型就是js这些
string
let str1 = "" // 自动推导为string
let str2:string = ""
// String推荐小写,大写留给接口、类型别名,规范把
let str3:String = ""
str1 = 2 // 报错,number不能赋值给string
number && boolean && symbol
let num:number = 1
let bool:boolean = true
// Symbol创建的值永远不相等
let sym:symbol = Symbol("2")
undefined
let und:undefined;
und = null // 报错
// und的值只能是undefined,''/null都不行
null
// nul的值只能为null
let nul:null;
需要注意,undefined&&null
null和undefined直接赋值给变量,变量的类型不是值本身而是any,就是任意类型,一旦被设置了any,ts将毫无意义。
ts非原始数据类型
object,Object,{}
object(推荐)
// 小写的o,object
let obj:object = { a: 1 } // √
let arr:object = [1] // √
let num:object = 2 // × 不能将number赋给object
Object && {}
Object类型和{}类型一致,基本数据类型校验都能通过,除了undefined 和 null,类型太过广泛,不被推荐
// 大写的O,Object
let sym:{} = Symbol("z") // √
let str:{} = "" // √
let nul:{} = null // 报错,不能将null类型赋值给‘{}’类型
let und:{} = undefined // 报错
let bol:Object = true // √
let num:Object = 2 // √
let nul:Object = null // 报错
let und:Object = undefined // 报错
数组类型
第一种写法:
// number[],数组里都是number类型
let arr:number[] = [3, 6, 9]
arr[0] = 2 // √
arr[1] = true // 报错,不能将boolean类型赋值给number类型
第二种写法:
// 泛型,类型参数化,功能等效于number[]
let arr:Array<number> = [5, 6, 7]
元组:
// 类型一一对应,若数组长度大于或小于自身长度则报错
let arr:[number, string, boolean] = [1, "", false]
联合类型
联合类型 -> '|', 或
①
// 变量numAndBol为number或boolean类型
let numAndBol: number | boolean = 1
numAndBol = false
numAndBol = "" // 报错
②
let variable:1|'2' = 1 // √
variable = '2' // √
variable = 2 // ×
变量variable的类型是值,为1或'2'
③
// 可以都有,至少得有1个
let obj: {a:1} | {b:2};
obj = {a:1} // √
obj = {b:2} // √
obj = {c: 3} // 报错
obj = {a:1, b:2} // √
交叉类型
交叉类型 -> '&', 与
// 必须都要有
let obj:{name: string, gender: number} & {isSmart: boolean};
// 类型不对,缺少或多出,都会报错
obj = { name: 'zs', gender: 1, isSmart: true} // √
obj = { name: 'zs', gender: 1 } // ×
// 如果一个属性出现多次类型设置,需要同时满足
let obj:{name: string, gender: number} & {gender: 2};
obj = { name: 'hh', gender: 1 } // ×
// 所以这里gender只能为2
obj = { name: 'xx', gender: 2 } // √
any
any 类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any 能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型。
let power: any;
// 赋值任意类型
power = '123';
power = 123;
// 它也兼容任何类型
let num: number;
power = num;
num = power;
unknown
使用 unknown 类型代替 any 类型 ???
any是类型系统提供给我们的一个妥协方案,在无法确定当前类型时就使用any,它可以赋给任何类型值,也可以接受任何类型值- 使用
any替代之后,这样我们就失去了类型检查的作用,在可能出错的地方也不会发现错误。
- 使用
any还会造成类型污染的问题,any类型的对象会导致后续的属性类型都会变成any
失去了类型检查作用之后,TS 不会在开发或者编译时提示哪里可能出错,既然我们选择了使用 TS,那么在开发中就尽量避免使用 any ,以便 TS 能够帮助我们做更多的事情。
所以从 TypeScript 3.0 起就引入了一个新的基础类型 unknown 作为一个类型安全的 any 来使用。任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。
如果要把 unknown 类型值赋给 unknown 或者 any 之外的其它类型,或者对 unknown 类型执行方法调用或者属性读取之类的操作,都必须先使用条件控制流或者类型断言来收窄 unknown 到指定的类型
void
void:没有返回值
// 无返回值相当于就是return undefiend
function fn ():void {}
let friday = fn()
// 只能是undefined,是其他都会报错
friday = undefined
never
never:产生异常可以使用它,同时never是所有类型的子类型,说白了never就是没有类型
猜想:
验证:
never是所有类型的子类型?验证:
接口类型 interface
interface是用于自定义类型的,可以分别给对象、数组、函数使用。
给对象用
// 利用接口,自定义类型
interface ObjItf{
name: string,
age: number,
isGirl: boolean
}
// 3个属性必须都要有,多或少或类型不对都报错
let obj:ObjItf = {
name: 'xiaohua',
age: 24,
isGirl: false
} // √
给数组用
interface ArrItf {
// 利用接口来给数组定类型时的固定写法
// [index:number]下标类型:值类型
[index:number]: number | string
}
let arr:ArrItf = [1, 2, '3']
给函数用
interface FuncItf{
// 固定写法
// 形参及类型:返回值类型
(name:string): void
}
let fn:FuncItf = (name:string) => {}
// 或 let fn:FuncItf = (name:string):void => {}
fn("")
接口的继承、同名、缺省、只读
接口继承
interface NameItf{
name: string
}
interface GenderItf{
gender: number
}
// extends:继承
// InfoItf接口拥有被继承两个接口的所有属性和对应类型
interface InfoItf extends NameItf, GenderItf {
isHappy: boolean
}
// 缺少或多出或类型不对或属性名不对则报错
let person:InfoItf = {
name: '张三',
gender: 1,
isHappy: true
} // √
let person:InfoItf = {
name: '张三',
gender: 1
} // ×
接口同名
interface ObjItf{
phone: number
}
interface ObjItf{
age: string
}
let obj:ObjItf = {
phone: 7999,
} // ×,缺少age
obj = {
age: '24'
} // ×,缺少phone
// 变量类型是接口,且为同名接口,
// 必须要包含里面所有属性且类型正确
obj= {
phone: 7999,
age: '24'
} // √
缺省 和 只读
interface ObjItf {
readonly name: string // readonly 设置属性为只读
age?: number // 缺省 ? 可以没有这个属性
/*
若使用了ObjItf接口,需要定义fn函数,参数
是布尔值,返回值也是布尔值。
*/
fn: (bool: boolean) => boolean
}
// 可以不写age,因为缺省了
let obj:ObjItf = {
name: 'xh',
fn: bool => bool
} // √
// 报错,因为name是只读属性
// Cannot assign to 'name' because it is a
// read-only property
obj.name = '' // ×
联合交叉类型
console.log(1 || 2&&3) // 1 || 3 -> 1
&& 优先级高于 ||,& 同样优先级高于 |
// 满足联合类型‘|’左边或右边的,& 需同时满足
let obj: {name: string} & {age: number} |
{name: string, age: string, bol: boolean}
obj = {
name: 'sz',
age: 24
} // √
obj = {
name: 'sz',
bol: false
} // ×
obj = {
name: 'hh',
age: '24',
bol: false
} // √
类型别名 type
// 自定义类型
type StrOrNum = string | number
let num:StrOrNum = 2 // √
num = '2' // √
type ObjType = {
isHappy: boolean,
fn?: (name:string) => number
}
let obj:ObjType = {
isHappy: false,
} // √
obj = {
isHappy: true,
fn: MYName => 666
} // √
interface和type区别:
- 都可以用来自定义类型
- 类型别名type不支持重复定义,接口可以
3. 类型别名、接口 混合使用
interface NameItf{
name: string
}
type NameType = NameItf['name'] // string
let n:NameType = 'zs' // √
// Type 'number' is not assignable to type 'string'
n = 24 // ×
函数
①
// 参数1,2是number类型,返回值也是。
function fn(arg1:number, arg2:number):number{
return arg1 + arg2
} // √
const fn2 = (a: boolean, b: string):number => {
return 1
} // √
② 接口定义函数类型
interface FnItf {
(name:string): number
}
let fn:FnItf = (name:string) => {
return 1
}
fn("")
③ 类型别名定义函数类型
type FnType = (name:string) => number
let fn:FnType = (name:string) => {
return 1
}
fn("")
④ 函数作为对象属性
interface ObjItf{
fn: FnItf
}
interface FnItf{
(bol:boolean): void
}
const obj:ObjItf = {
fn: bol => {}
}
obj.fn(true) // √
// Argument of type 'number' is not assignable
// to parameter of type 'boolean'
obj.fn(1) // ×
或者这样
type ObjItf = {
fn: FnItf
}
interface FnItf{
(bol:boolean): void
}
const obj:ObjItf = {
fn: bol => {}
}
或者这样(写法很多,看自己喜好)
type ObjItf = {
fn: FnItf
}
type FnItf = (bol:boolean) => void
const obj:ObjItf = {
fn: bol => {}
}
函数参数
① 默认参数
const fn = (a:number, b = 1) => {
return a + b
}
fn(1,2)
fn的类型
② 缺省参数
const fn = (c:boolean, d?:string) => {
if (d) {
return c + d
}
return c
}
fn(true, '2')
fn(false)
fn的类型
③ 剩余参数
const fn = (a: boolean, ...args: Array<number>) => {
console.log(args); // [2,3,4]
}
fn(true, 2,3,4)
fn的类型
ts中的Promise
const p = new Promise((resolve, reject) => {
resolve({
code: 0,
data: [{ a: 1, b: 2 }, { a: 6, b: 9 }]
})
})
// res: unknown
p.then(res => {
if (!res.code) {}
})
res是unknown类型,所以不能‘.’,不能调用方法。
所以需要给res设置类型:
// 给res定义类型
interface ResItf{
code: number,
data: {a: number, b: boolean}[],
msg: string
}
const p:Promise<ResItf> = new Promise((resolve, reject) => {
resolve({
code: 0,
data: [{ a: 1, b: true }, { a: 6, b: false }],
msg: 'success'
})
})
p.then(res => {
if (!res.code) {}
})
枚举
枚举不是用来定义类型,而是用来列举数据用的
enum Xxx{
x: 1,
y: 2
}
// Xxx.y
没有使用枚举前:
let code:number|string = 200
if (code === 200) {
console.log("成功")
} else if (code === 400) {
console.log("失败,请求参数问题")
} else if (code === 500) {
console.log("失败,服务器问题")
}
使用枚举后:
// 好处是状态统一管理,易于维护
enum StatusCode{
success = 200,
paramsError = 400,
serverError = 500
}
let code:number|string = 200
if (code === StatusCode.success) {
console.log("成功")
} else if (code === StatusCode.paramsError) {
console.log("失败,请求参数问题")
} else if (code === StatusCode.serverError) {
console.log("失败,服务器问题")
}
枚举小知识:
// 递增
enum Eq{
a, // 0
b, // 1
c // 2
}
// 默认从0开始递增,有值则在原有基础上递增
enum Lz{
d, // 0
e: 6, // 6
f // 7
}
泛型的基本用法
泛型:类型参数化
没写泛型之前,定义函数是这样的
function fn(n:number):number {
return n
}
fn(100)
fn(true) // 报错
若使fn(true)不报错,可以这样做:
function fn(n:number|boolean):number|boolean {
return n
}
fn(100)
fn(true)
// fn('') 报错
若fn函数想传字符串,依然会报错,还得再声明类型,而泛型简化了该流程。
function fn<T>(n: T):T {
return n
}
// <boolean>,boolean是实参,T是形参,boolean传给T
fn<boolean>(true) // √
fn<string>('') // √
fn<'haha'>('haha') // √ 值就是类型
fn<number|string>(2) // √
泛型参数可以传多个,比较灵活
function fn<T, K>(n: T, m: K):T {
return n
}
fn<boolean, number>(true, 1) // √
fn<string, symbol>('', Symbol(1)) // √
fn<'haha', null>('haha', null) // √ 值就是类型
fn<number|string, (name:string) => void>(2, function(x){
console.log('只要不return都ok,因为返回值是void')
}) // √
泛型在类型别名和接口上的应用
泛型-类型别名
没有使用泛型之前,类型别名是这么用的
type ObjType = {
name: string,
fn: () => boolean
}
let obj:ObjType = {
name: 'xm',
fn: () => {
return true
}
}
泛型 + 类型别名
type ObjType<T, G> = {
name: T,
fn: () => G
}
// 类型传给T,G
let obj:ObjType<string, boolean> = {
name: 'xm',
fn: () => {
return true
}
} // √
改造一下:
type StrOrNum = string | number
type ObjType<T, G> = {
name: T,
fn: () => G
}
// 泛型中<>也可以使用类型别名(StrOrNum)
let obj:ObjType<StrOrNum, boolean> = {
name: 'xm',
fn: () => {
return true
}
} // √
泛型-接口
没有使用泛型之前,接口是这么用的
interface ObjItf{
name: string,
arr: Array<number|string|boolean>,
fn: () => { code: string }
}
let obj:ObjItf = {
name: 'xm',
arr: [1, '2', false],
fn: () => {
return { code: '1' }
}
}
泛型 + 接口
interface ObjItf<A, B, C>{
name: A,
arr: B,
fn: C
}
type StrOrNum = string|number
type ArrType = Array<number|string|boolean>
type FnType = () => { code: string }
// 类型传给A.B.C
let obj:ObjItf<StrOrNum,ArrType,FnType> = {
name: 'xm',
arr: [1, '2', false],
fn: () => {
return { code: '1' }
}
}
泛型约束-extends
先来看下泛型使用
// 类型别名+泛型
// 给T和G传什么类型都行,T在这里是string
type objType<T,G> = {
name: T,
fn: () => G
}
// 我传什么,泛型的类型就是什么
// 泛型可以规定’我们‘一定要传什么类型吗?
let obj:objType<string, boolean> = {
name: '',
fn: () => true
}
泛型的出现是为了’类型‘传的更随便,但是可以约束泛型的类型吗?
// 可以的
// 泛型G使用默认值,没给G传类型,就使用默认类型
type StrOrNum = string|number
type objType<T extends StrOrNum, G=number> = {
name: T,
fn: () => G
}
// T只能是string或number,因为被extends限制了类型
let obj:objType<string, boolean> = {
name: '',
fn: () => true
}
泛型条件分配
例子:
// ③
type Str = string
// ②
type FxType<H> = H extends Str ? string : boolean
// ①
const whatValue:FxType<string> = 'string extends string' // √
改造一下:
type Str = string | number
type FxType<H> = H extends Str ? string : boolean
const whatValue:FxType<string> = 'string extends Str'
再改造下:
type Str = string
type FxType<H> = H extends Str ? string : boolean
const whatValue:FxType<string | number> = 'what whole type?'
解释:
- 特殊:如果泛型传递的参数是联合类型的话,那么会用其中一种类型一个一个的来与extends右侧进行比对
- string extends string,结果是string
- number extends string,结果是boolean
eq:将上面的例子拆解,为什么trys的类型变成boolean了?
上面泛型extends比对是特例,下面的结果才是正常思维现象。得出的结果可以这么理解,extends 右边比左边就是牛逼,右边大于等于左边才为真,否则就为假。
再看个例子:
type Str = string
type StrOrNum = string | number
type JudgeType<T> =
T extends Str ? string :
T extends StrOrNum ? symbol : boolean
let variable: JudgeType<string | number>
类型是 string|symbol
如何进行完整匹配
加个中括号
type Str = string
type FxType<H> = [H] extends [Str] ? string : boolean
const whatValue:FxType<string | number> = '' // 报错
- [string | number] extends [string] ? string : boolean
- 类型为boolean
extends的条件判断
类型判断
type A = {
name: string,
age: 3,
canWan: boolean
}2
type B = {
name: string
}
// 类型为值类型,为false
type JudgeType = B extends A ? true : false
let val:JudgeType = false // √
- 类型对象 extends 类型对象,B extends A
- B包含A,并且B中属性多于A,条件判断为真
- B中属性只要少于A,就为假
联合类型判断
type A = string
type B = string | number
const v1: A extends B ? string : boolean = '' // √
const v2: B extends A ? string : boolean = false // √
extends的几个意思
B继承A,B包含A所有规范并可以额外指定规范,有谁使用了B这个规范,则要满足B、B extends A的所有规范
type A = { name: '' }
interface B extends A {
age: number
}
let obj:B = {
age: 2,
name: ''
}
看个泛型案例:
目的:extends约束传递给函数的参数
function fn<T extends { id: number, record(n: number): number}>(arr: T){}
fn([]) // √
fn([{}]) // ×,既然定义了{},就要规范完整
fn([{ id: 1, record(){ return 1 }}]) // √
fn([{ id: 1, record(n){ return n }}]) // √
- arr:T[],参数是数组,没有返回值,问题来了,T是什么?
- 为了搞清楚T是什么,有必要看段代码理解一下
// 声明常量arr,是个数组[],数组内值的类型是number
const arr:number[] = [1,2,3]
// 拿这个fn传的参数举例
fn([{ id: 1, record(n){ return n } }])
// T就是{ id: 1, record(n){ return n } }类型
- fn([{ }]) 报错的原因就是T中必须要有id和record
类的定义(类-接口)
该如何写呢?
注意点:在定义类的同时,相当于定义了一个同名的接口
class Person{
age: number
constructor(age: number) {
this.age = age
}
getSomething () {
return '随意~'
}
}
new Person(18)
// Person接口
let obj:Person = {
age: 1,
getSomething () {
return ''
}
}
该Person类如果用接口的写法该如何写?
// 这里
interface Person{
age: number
getSomething: () => string
}
let obj:Person = {
age: 1,
getSomething () {
return ''
}
}
类的继承
class Person {
myName: string
constructor(name: string) {
this.myName = name
}
getName() {
return this.myName
}
}
class Male extends Person {
// 类的属性age规定是number类型
age: number
// 参数maleAge规定是number类型
constructor(maleName: string, maleAge: number) {
// 相当于调用父类constructor并将参数传入
super(maleName)
this.age = maleAge
}
// 重写(子类有执行子类,没有则用父类【new Male】)
getName() {
return 'I~am ' + this.myName
}
}
const m = new Male('黄晓明', 32)
console.log(m.myName) // 黄晓明
console.log(m.getName()) // I~am 黄晓明
console.log(m.age) // 32
类的修饰符
- public: 默认修饰符,类内外都可访问
- private: 私有,只允许本类访问
- protected: 受保护的,本类和子类可以访问,类外不行
private
protected
类的静态属性
静态属性/方法只能由类调用,类实例访问不到
class Person {
static text: string = 'hi'
static getText() {
return this.text
}
}
console.log(Person.text)
new Person().text // ×
console.log(Person.getText())
new Person().getText() // ×
抽象类和接口
抽象类
- 抽象类是描述,描述着继承它的类应该具有什么属性、方法
- 哪个类继承了抽象类,就得实现抽象类中定义的抽象属性和抽象方法
- <用抽象类来规范’继承该抽象类的普通类‘>
abstract class Person {
abstract name:string
abstract isTrue(): boolean
getAge() {
return 24
}
}
class Male extends Person {
name = ''
isTrue() {
return true
}
}
const m = new Male()
console.log(m.getAge) // 24
- 抽象类不能被实例化
不能创建一个抽象类的实例
接口
// 给类定义了规范
interface clsItf{
name: string
getName: () => void
}
// 类去实现了该规范
class Person implements clsItf{
name=''
getName(){}
} // √
类型断言 as
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。
const obj = {};
obj.num = 123; // Error: 'num' 属性不存在于 ‘{}’
obj.str = 'hello'; // Error: 'str' 属性不存在于 '{}'
这里的代码发出了错误警告,因为 obj 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 num 或 str,你可以通过类型断言来避免此问题:
interface OItf {
num: number;
str: string;
}
const obj = {} as OItf;
obj.num = 123;
obj.str = 'hello';
通过类型断言,排除自己不需要的
type User = { name: string, age: number, get(n: string): void }
type FilterType <T,U> = {
[K in keyof T as Exclude<K, U>]: T[K]
}
type ValueType = FilterType<User, 'name'>
怎么理解?拆解下
① 这相当于什么都没干
② 加上as
keyof
keyof一般跟在接口后面,keyof 接口 -> 表示它的类型是接口的某一个属性
interface EqItf{
isHappy: boolean
yourName: string
myAge: number
}
type EqType = keyof EqItf
let eq:EqType
eq = 'isHappy' // √
eq = 'yourName' // √
eq = 'myAge' // √
eq = 'isHappya' // ×
interface EqItf{
isHappy: boolean
yourName: string
myAge: number
[key1: number]: {} // 属性是number类型的
[key2: string]: []
}
// 只看EqItf的key
type EqType = keyof EqItf
let eq:EqType
eq = 1 // √
eq = '' // √
扩展:
①
②
③
即使规定了obj和key的类型,但obj[key]依然会报错,为什么?
因为ts认为,这个key不一定是在obj中,所以得换个写法
function getAttribute<T>(obj: T, key: keyof T):T[keyof T] {
return obj[key]
}
const user = { name: 'lwy', age: 24 }
getAttribute(user, 'age')
- 代入,user相当于T
- key:keyof T,说明key的值一定是在user中
in
’遍历’
type StrOrNum = string | number
type OType = {
// 对象属性可以是string或number类型
// 属性值为boolean类型
[key in StrOrNum]: boolean
}
let obj:OType = {
'ha': true,
89: false
}
typeof
提取变量或对象的类型
提取变量
let word = 'nihaots'
type VType = typeof word
let str = '' // √
str = 1 // ×
提取对象
let o = {
age: 2,
fn: (x:string) => 2
}
type OType = typeof o
let obj:OType = {
age: 6,
fn: () => {
return 7
}
} // √
obj.fn(2) // ×
infer在ts中的使用方法
infer P,表示声明一个待推断的类型变量P
需求:取类型
type TTtype = {
name: string
age: number
}
type fetchType<T> = T extends { name:infer M, age:infer M } ? M : T
/**
T extends { name:infer M, age:infer M } ? M : T
这句话的意思是:如果T能赋值给{ name:infer M, age:infer M },
则结果是{ name:infer M, age:infer M }类型中的参数M,否则返回P
*/
let val:fetchType<TTtype>
- infer M,相当于是定义一个待推断的类型。这个M是什么类型取决于T中属性参数的类型
- 第一个M是string,第二个M是number,所以val就是联合类型:string | number
没用infer可以吗?
不行哈!记住,infer是在声明一个待推断的类型
两个都是M?写其他的可以吗?
各式各样,都可以
扩展
①通过遍历来获取类型
type User = { name: string, age: number, get(a: string): void }
type GetType<T> = {
[K in keyof T]: T[K] extends (infer U) ? U : K
}
type valueType = GetType<User>
现在离取类型只差最后一步
解释:
- T是User,keyof User -> "name" | "age" | "get"
- [K in keyof T],遍历出"name","age","get"
- 推导
GetType<User> = {
// 对于name而言,T[K]是string
name:T[K] extends (infer U) ? U : K
// 对于age而言,T[K]是number
age:T[K] extends (infer U) ? U : K
// 对于get而言,T[K]是((a:string)=> void)
get:T[K] extends (infer U) ? U : K
}
GetType<User> = {
name:string extends (infer U) ? U : K
age:number extends (infer U) ? U : K
// 对于get而言,T[K]是
get:((a:string)=> void)) extends (infer U) ? U : K
}
infer U,是在声明一个待推断的类型变量,变量名为U
- 所以,name一定是string,age一定是number,get一定是(a:string)=> void),也就是这样:
如何取出它的类型呢?完整代码如下
type User = { name: string, age: number, get(a: string): void }
type GetType<T> = {
[K in keyof T]: T[K] extends (infer U) ? U : K
}[keyof T]
type valueType = GetType<User>
相当于就是一个完整对象+[属性1,属性2,属性3],如何理解?
let obj = { name: '', age: 2 }
// obj['name'] obj['age']
// { name: '', age: 2 }['name']
// { name: '', age: 2 }['age']
② 推断出函数返回值类型
type Fn = (n: string) => number[]
type GetFnReturnValue<T> = T extends ((...args: any) => (infer U)) ? U : T
type valueType = GetFnReturnValue<Fn>
命名空间 namespace
一个变量(a)在不同文件声明,就会报重复声明的错误,命名空间的出现就是解决这个问题,命名空间可以类比为匿名函数。
namespace ObjNs {
export type NumOrStr = number | string
}
let obj:ObjNs.NumOrStr = 99
- 命名空间本质上是一个对象,作用是将一系列相关的全局变量统一起来
- 没export导出会报错
通过值类型过滤掉索引
目的是根据值类型排除掉不想要的,再复制出剩下的重组成新的类型规范
type User = { name: string, age: number, get(n: string): void }
type FilterProperty<T, U> = {
[K in keyof T]: T[K] extends U ? never : K
}[keyof T]
type ValueType = Pick<User, FilterProperty<User, Function>>
什么意思?拆解理解下:
① 这段代码的目的是,根据类型排除掉不想要的那一组键值对
type User = { name: string, age: number, get(n: string): void }
type FilterProperty<T, U> = {
[K in keyof T]: T[K] extends U ? never : K
}
type ValueType = FilterProperty<User, Function>
never相当于无类型,所以ValueType类型只剩下name和age两组
② 取出根据类型排除后的值(这个值相当于key)
type User = { name: string, age: number, get(n: string): void }
type FilterProperty<T, U> = {
[K in keyof T]: T[K] extends U ? never : K
}[keyof T]
type ValueType = FilterProperty<User, Function>
③ 最后就是使用Pick工具类型筛选出自个想要的键值对
type User = { name: string, age: number, get(n: string): void }
type FilterProperty<T, U> = {
[K in keyof T]: T[K] extends U ? never : K
}[keyof T]
type ValueType = Pick<User, "name" | "age">
泛型工具类型
Partial
所有属性都是可缺省(?:),可写可不写
interface PItf {
name: string
age: number
}
let obj: Partial<PItf> = {
} // √
Partial是这么定义的:
type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
- 类比,T相当于PItf这个接口,Partial是给对象使用的
- keyof T -> keyof PItf -> name 或 age
- in就是遍历,[P in keyof T] -> name 或 age
- T[P] -> PItf['name'] 或 PItf['age'] -> string | number
type Partial<PItf> = {
name?: string | undefined
age?: number | undefined
}
相当于
Required
所有属性都必填,即使设置为可缺省(?:)
interface PItf {
name: string
age: number
isTs?: true
}
let obj:PItf
obj = {
name: '',
age: 24
} // √
消除报错
interface PItf {
name: string
age: number
isTs?: true
}
let obj:Required<PItf>
obj = {
name: '',
age: 24,
isTs: true // 只能为true
}
Required是这么定义的:
type Required<T> = { [P in keyof T]-?: T[P]; }
- [P in keyof PItf] -> name 或 age 或 isTs
- -? -> 抵消问号
- T[P] -> string | number | true
整合:
type Required<PItf> = {
name: string
age: number
isTs: true
}
最终,变成全部必填的了
Readonly
属性都只读,不允许修改
interface RItf {
teacher: string
num: number[]
}
let obj:Readonly<RItf> = {
teacher: 'xc',
num: [2,8,2,5,6]
}
obj.teacher = '' // ×
Readonly是这么定义的
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
- T -> RItf, keyof T -> teacher 或 num
- [P in keyof T] -> teacher 或 num
- T[P] -> 对应着就是string,number[]
整合
type Readonly<RItf> = {
readonly teacher: string
readonly num: number[]
}
Pick
type-类型别名,interface-接口。都是用于规范类型的。Pick存在的意义是,可以选择性地复制出定义在type或interface中的属性及类型
Pick有两个参数
- 选择谁的属性
- 选择哪几个属性
// type PType = Pick<A, '属性'>
// 从A中选出哪几个属性构造出新的类型
/**
type Props = {
title: string
id: number
num: Array<number>
}
*/
interface Props{
title: string
id: number
num: Array<number>
}
type PType = Pick<Props, 'title' | 'id'>
Pick是这么定义的
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
-
T -> Props,keyof T -> title 或 id 或 num
-
K extends keyof T -> 限制了参数的传值,K的值一定是 title 或 id 或 num 或它们的组合(title | id)
-
[P in K] -> 看Pick的第二个参数传了什么,那么P就是它们的其中之一
-
T[P] -> 就是属性对应的类型
Record
Record<Keys, Type>
列举出某些属性,把它们规定成一致的类型并构造出给对象使用的规范。
eq:
type RType = Record<'name'|'age'|'height', string>
// 必须要有RType声明的所有属性
let obj:RType = {
name: '',
age: '',
height: ''
}
// type RType = Record<'name'|'age'|'height', string>
// 相当于
type RType = {
name: string
age: string
height: string
}
Record是这么定义的
type Record<K extends string | number | symbol, T> = { [P in K]: T }
- K extends string | number | symbol -> 属性必须是string或者number或symbol的其中一种
- [P in K] -> string 或 number 或 symbol
- T -> 类型,属性值的类型
Extract
从type定义的规范中,保留需要的类型
type A = string | number | boolean
// 我只要number和boolean
const val1:Extract<A, number|boolean> = 2
const val2:Extract<A, number|boolean> = true
Extract是这么定义的
type Extract<T, U> = T extends U ? T : never
- 代入下,A就是T,U就是number | boolean
- 泛型extends,需要一个个比对
- string extends number | boolean,结果为never
- number extends number | boolean,结果为number
- boolean extends number | boolean,结果为boolean
- 综合答案为:never | number | boolean,因为never是所有任意类型的子类型,所以最终答案为number | boolean
Exclude
从type定义的规范中,排除不需要的类型
type Sb = string | boolean
type Sbn = string | boolean | number
// A 中排除 B
let num:Exclude<Sbn, Sb> = 2
Exclude是这么定义的
type Exclude<T, U> = T extends U ? never : T
与Extract恰好相反,排除不需要的。
Omit
根据属性名排除,选出想要的
type Info = { name: string, age: number, city:string }
type valueType = Omit<Info, 'name' | 'age'>
Omit是这么定义的
type Omit<T, K extends string | number | symbol> = {
[P in Exclude<keyof T, K>]: T[P]
}
- 代入,T是Info,Omit的第二个参数只能是string | number | symbol
- Exclude<keyof T, K>,keyof Info -> "name" | "age" | "city",K是'name' | 'age',Exclude是从"name" | "age" | "city"排除"name" | "age",所以结果为"city"
- [P in Exclude<keyof T, K>],[P in "city"],结果就是"city"
- T[P]就是string
- 所以Omit就是根据属性名排除不需要的,留下排除之后的键值对。
手写Omit + 测试
type Info = { name: string, age: number, city:string }
type MyOmit<I,G> = Pick<I, {
[P in keyof I]: P extends G ? never : P
}[keyof I]> // city
type ValueType = MyOmit<Info, 'name' | 'age'>