当前位置:网站首页>typescript学习笔记
typescript学习笔记
2022-08-03 18:26:00 【AIpoem】
主要用于自查,对我自己比较熟悉的部分进行了删减
学习的话建议看我的参考原文:https://juejin.cn/post/7003171767560716302#heading-60
目录
基础类型
Tuple元组
可以定义内部为不同类型的数组
let a: [string, number]
a = ['poem', 20] // ok
a = [20, 'poem'] // error
️:元组类型只能表示 已知元素数量 和 已知元素类型 的数组,长度也已经指定。
undefined和null
undefined 和 null 是所有类型的子类型
可以把 undefined 和 null 赋值给任何类型的变量
(在未开启 strickNullChecks 严格模式检查的情况下)
void
表示没有任何类型
声明一个 void 类型的变量没有什么大用,一般只在 函数没有返回值 时去声明
️:函数没有返回值将得到undefined,但要定义成void类型,而不是undefined类型
any和unknown
any 会跳过类型检查器的检查
任何值都可以赋值给 any 类型any 类型也可以赋值给任何类型
let a: any = 4
a = 'poem' // ok
a = 20 // ok
任何值也都可以赋值给 unknown 类型
但是 unknown 类型只能赋值给 unknown 和 any
// 任何值可以赋给unknown
let a: unknown = 4
a = 'poem' // ok
a = 20 // ok
// unknown能赋值给unknown
let b: unknown = 5
b = a // ok
// unknown能赋值给any
let c: any = 6
c = a // ok
// unknown不能赋值给unknown和any以外的其他类型
let d: number = 7
d = a // error
如果不 缩小类型 ,就无法对 unknown 类型执行任何操作:
const getNumber = () => {
return '123'
}
const obj: unknown = {
number: getNumber
}
obj.getNumber() // error, 类型unknown上不存在getNumber属性
我们可以使用 typeof 、类型断言 等方式来缩小未知范围:
const getName = () => {
let name: unknown
return name
}
const name = getName()
// 直接使用
const lowerName = name.toLowerCase() // error
// 利用typeof缩小范围
if(typeof name === 'string') {
const lowerName = name.toLowerCase() // ok
}
// 利用类型断言缩小范围
const lowerName = (name as string).toLowerCase() // ok
never
表示 永远不存在 的值的类型
值永不存在的两种情况:
- 一个函数执行时抛出了
异常,那这个函数永远不存在返回值(抛出异常会中断程序执行,使得程序运行不到返回值那一步) - 函数中执行
无限循环的代码,使得程序永远无法运行到返回值那一步
// 异常
const err = (msg: string): never => {
throw new Error(msg)
}
// 死循环
const loopForever = () => {
while(1) {
}
}
never 和 null 、undefined 一样
也是任何类型的子类型,也可以赋值给任何类型
但是任何类型都不能赋值给 never 类型( never 本身可以)( any 也不行)
let a: never
let b: never
let c: any
a = 123 // error number类型不能赋值给never类型
a = b // ok never类型可以赋值给never类型
a = c // error any类型不能赋值给never类型
类型断言
两种方式实现:
1.尖括号语法
2.as语法
// 尖括号语法
let a: any = 'poem'
let aLength: number = (<string>a).length
// as语法
let a: any = 'poem'
let aLength: number = (a as string).length
类型推论
如果没有明确地指定类型,那么ts会依照类型推论的规则推断出一个类型
let a = 'poem'
// 等价于
let a: string = 'poem'
如果定义的时候没有赋值,之后不管有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let a;
a = 'poem' // ok
a = 20 // ok
联合类型
表示取值可以是多种类型中的一种,用|分隔每个类型
let a: string | number
a = 'poem' // ok
a = 20 // ok
交叉类型
将多个类型合并为一个类型,使用&定义
interface A {
name: string
}
interface B {
age: number
}
let a: A & B = {
// ok
name: 'poem',
age: 20
}
a 既是 A 类型也是 B 类型
️:如果 key 相同而 类型 不同,则 key 为 never 类型
interface A {
name: string
}
interface B {
name: number
}
let a: A & B = {
name: 'poem' // error 类型string不能分配给类型never
}
let a: A & B = (() => {
throw new Error() // ok
})()
interface接口
为什么赋值使得类型检测变宽松了
看下例:
1
interface ParamType {
name: string
}
const test = (param: ParamType) => {
console.log(param.name)
}
test({
name: 'poem',
age: 20
}) // error
2
interface ParamType {
name: string
}
const test = (param: ParamType) => {
console.log(param.name)
}
let obj = {
name: 'poem',
age: 20
}
test(obj) // ok
例1中,在参数里写对象,这就相当于直接给 param 赋值,这个对象有严格的类型定义,必须符合 ParamType 类型,所以不能多 age 这个属性
例2中,在外面用一个变量 obj 来接收这个对象,obj 不会经过额外属性检查,然后将 obj 再复制给 param ,根据类型的兼容性,都具有 name 属性,所以被认定为两个相同,可以利用此法来绕开多余的类型检查
只读属性
属性名前用readonly关键字来指定只读属性
interface MyType {
readonly name: string
readonly age: number
}
ReadonlyArray
对于数组,ts还有 ReadonlyArray<T> 类型,此类型将数组的所有可变方法去掉了,可以确保数组创建后再也不能被修改
let a: number[] = [1, 2, 3, 4]
let b: ReadonlyArray<number> = a
b[0] = 5 // error
b.push(5) // error
️:readonly声明的只读数组类型和ReadonlyArray声明的只读数组类型是等价的
绕开额外属性检查的方法
- 类型兼容
利用赋值操作,例子看的 - 类型断言
interface MyType {
name: string
}
let a: MyType = {
name: 'poem',
age: 20
} as MyType // ok
- 索引签名
ts支持两种索引签名:string 和 number
interface MyType {
name: string
[key: string]: string
}
let a: MyType = {
name: 'poem',
age: '20'
} // ok
️:一旦定义了,那确定属性和可选属性的类型都必须是它的类型的子集
interface MyType {
name: string // ok
age: number // error number类型不符合
gender: undefined // ok undefined是string的子集
[key: string]: string
}
可以同时使用 string 和 number 类型的索引
但是 number 索引的返回值必须是 string 索引的返回值类型的子类型
因为使用number来索引时,js会将它转换成string再去索引对象
class Animal {
name: string
}
class Dog extends Animal {
age: number
}
interface NotOkay {
[key: number]: Animal; // Error
[key: string]: Dog;
}
interface Okay {
[key: number]: Dog; // OK
[key: string]: Animal;
}
接口继承
接口继承接口使用关键字 extends ,子接口拥有父接口的类型定义
interface Father {
name: string
}
interface Son extends Father {
age: number
}
let s: Son = {
age: 20 // error
}
let s: Son = {
name: 'poem',
age: 20
}
支持多继承
interface Father {
name: string
}
interface Monther {
gender: string
}
interface Son extends Father, Monther {
age: number
}
let s: Son = {
// error
age: 20
}
let s: Son = {
// error
name: 'poem',
age: 20
}
let s: Son = {
// ok
name: 'poem',
age: 20,
gender: 'male'
}
️:使用多继承时,确保父接口之间没有共有属性,或共有属性的类型都相同(避免引发混乱)
// 两个父接口有共有属性name且类型不同
interface Father {
name: string
}
interface Monther {
name: number
}
interface Son extends Father, Monther {
// error
age: number
}
// 两个父接口有共有属性name,但是类型相同
interface Father {
name: string
a: string
}
interface Monther {
name: string
b: number
}
interface Son extends Father, Monther {
// ok
age: number
}
let s: Son = {
// ok
name: 'poem',
a: 'a',
b: 1,
age: 20
}
接口中的new
接口中有 new 是定义类 class 的类型
interface MyConstructor {
new (name: string, age: number): any
}
let a: MyConstructor = class {
} // ok
let b: MyConstructor = class {
// ok
constructor(public name: string) {
}
}
let c: MyConstructor = class {
// ok
constructor(public name: string, public age: number) {
}
}
// 参数多了,不兼容
let d: MyConstructor = class {
// error
constructor(public name: string, public age: number, otherProp: number) {
}
}
函数类型
函数表达式
const sum: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y
}
// 简写
const sum: (x: number, y: number) => number = (x, y) => {
return x + y
}
️:在ts的类型定义中,=> 用来表示函数的定义,左边是输入类型,要用括号括起来,右边是输出类型
不要跟箭头函数搞混了,例子的两个箭头含义就不同
用接口定义函数类型
interface SumType {
(x: number, y: number): number
}
const sum: SumType = (x, y) => {
return x + y
}
参数默认值
const buildName = (firstName: string, lastName: string = 'poem') => {
return `${
firstName}${
lastName}`
}
console.log(buildName('AI', 'xxx')) // AIxxx
console.log(buildName('AI')) // AIpoem
内置对象
DOM和BOM的内置对象
Document、HTMLElement、Event、NodeList等
let body: HTMLElement = document.body
let div: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (e: MouseEvent) => {
// do something
})
类数组对象IArguments:
function sum() {
let args: IArguments = arguments
}
Enum枚举类型
枚举是一种数据结构
数字枚举
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}
枚举成员会被赋值从0开始递增的数字:
console.log(Days.Sun) // 0
可以初始化枚举成员,初始化成员后的成员会在它的基础上自动增长1
enum Days {
Sun = 1,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}
console.log(Days.Mon) // 2
字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
类
public修饰符
类的属性、方法可以在外部访问。默认为public,可以不显式声明
private修饰符
类的属性、方法不可以在外部访问
protected修饰符
类的属性、方法只在子类中可以访问
class Animal {
protected name: string = 'poem'
private age: number = 20
}
class Dog extends Animal {
getName() {
console.log(this.name) // ok
}
getAge() {
console.log(this.age) // error
}
}
参数属性
可以在类的构造函数上对参数使用public、private、protected修饰符,它的作用是可以更方便地让我们在一个地方定义并初始化一个成员
class Animal {
constructor(public name: string, private age: number) {
}
}
// 等价于
class Animal {
public name: string
private age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
方便使用:
class Animal {
constructor(public name: string, private age: number) {
}
getName() {
console.log(this.name)
}
}
let a = new Animal('rabbit', 1)
a.getName() // 'rabbit'
抽象类
以abstract开头的类是抽象类
抽象类不能用来创建实例对象,专门用来被继承
抽象类中的抽象方法不包含具体实现,必须在派生类中实现
abstract class Animal {
constructor(public name: string, public age: number){
}
// 要在派生类中实现此抽象方法
abstract getName(): string
}
class Dog extends Animal {
constructor(public name: string, public age: number, public gender: string) {
// 在派生类的构造函数中必须调用基类的构造函数
super(name, age)
}
getName(): string {
return this.name
}
}
let a: Animal // ok 可以创建一个对抽象类型的引用
a = new Animal() // error 不能创建一个抽象类的实例
a = new Dog() // ok 可以创建一个抽象子类的实例
类实现接口
用类实现接口,使用关键字implements
interface PersonType {
name: string
sayHi(): void
}
class Person implements PersonType {
constructor(public name: string) {
}
sayHi() {
console.log('hi!')
}
}
构造函数不在检查范围内
看一个例子:
interface Person {
new(name: string): any
}
// error 类型People提供的内容和签名:‘new(name: string): any’不匹配
class People implements Person {
constructor(name: string) {
}
}
原因是:当类实现一个接口时,只对 实例部分 进行静态检查,而 constructor 在 静态部分 ,不在检查范围内(这里的静态部分指构造函数,静态属性或静态方法都直接挂在构造函数上)
解决方法:再定义一个新的不带构造器签名的接口,让类实现这个接口。利用类型的兼容性,做到构造函数的类型检查
可以利用函数表达式来书写
interface Person {
new(name: string): any
}
interface newPerson {
name: string
}
let p: Person = class People implements newPerson {
constructor(public name: string) {
}
}
接口继承类
这个应该不常用️,看看就行
在ts声明一个类的时候,同时也声明了类的实例的类型let p: Person = new Person('poem'),第一个Person就是作为类的实例的类型存在,第二个Person作为构造函数存在
class Person {
constructor(public name: string, public age: number) {
}
}
interface aPerson extends Person {
gender: string
}
let a: aPerson = {
name: 'poem',
age: 20,
gender: 'female'
}
泛型
泛型约束
在函数内部使用泛型变量时,由于事先不知道它是哪种类型,不能随意操作它的属性或方法
const test = <T>(param: T): T => {
console.log(param.length) // error 类型T上不存在属性length
return param
}
泛型类型是不允许使用类型断言的,因为泛型表示任意类型
比如 string 类型无法断言为 object 类型
所以为了给泛型加上约束,我们需要使用继承接口来实现:
interface Length {
length: number
}
const test = <T extends Length>(param: T): T => {
console.log(param.length) // ok
return param
}
泛型接口
interface TestType {
<T>(param: T): T
}
const test: TestType = (param) => param
可以把泛型参数提前到接口名上
interface TestType<T> {
(param: T): T
}
const test: TestType<string> = (param) => param
泛型类
泛型也可以用在类的类型定义中
但要️:泛型类指的是实例部分的类型,类的静态属性不能使用泛型类型
class Person<T> {
name: T
static age: T // error
}
const p = new Person<string>()
泛型参数的默认类型
ts2.3后,可指定默认类型
当你没有直接指定类型参数,从参数中也推测不出类型时,这个默认类型就会起作用
const test = <T = string>(param: T): T => {
return param
}
类型守卫
看例子:
interface A {
name: string
age: number
}
interface B {
gender: string
}
const test = (param: A | B): void => {
if(param.name) {
// error
}
}
上面的写法会导致编译错误,因为无法确定在运行时 param 的类型是 A 还是 B(如果是A和B的共有属性是可以正常访问的)
我们可以使用断言来使其工作:
const test = (param: A | B): void => {
if((param as A).name) {
// ok
}
}
缺点:访问类型属性时要多次使用类型断言
我们可以使用类型守卫解决这类问题:
自定义类型守卫
定义一个类型守卫,只需要简单定义一个函数,它的返回值是一个类型谓词类型谓词就是指paramName is Type,paramName必须是来自当前函数签名里的一个参数名
const isA = (param: A | B): param is A => {
return (param as A).name !== undefined
}
const test = (param: A | B): void => {
if(isA(param)) {
// ok
}
}
返回值写成类型谓词的形式,就可以限定作用域中的类型
// 返回值是类型谓词
const isString1 = (param: any): param is string => {
return typeof param === 'string'
}
// 返回值是布尔值
const isString2 = (param: any): boolean => {
return typeof param === 'string'
}
// 使用
const isString1 = (param: any): param is string => {
return typeof param === 'string'
}
const isString2 = (param: any): boolean => {
return typeof param === 'string'
}
const test = (param: any): void => {
if(isString1(param)) {
// 编译时error
console.log(param.toFixed(2))
}
if(isString2(param)) {
// 编译时ok 运行时error
console.log(param.toFixed(2))
}
}
isString1()中的返回值是类型谓词,在它的if作用域中的param类型就被限定成了string,string类型没有toFixed()方法,所以编译错误
isString2()中的返回值是布尔类型,param类型未被限定为string,此时param的类型是any,会跳过类型检查
使用in操作符
判断属性存在与否
const test = (param: A | B): void => {
if("name" in param) {
// ok
}
}
typeof类型守卫
'const test = (param: string | number): void => {
if(typeof param === 'number') {
// ok
}
if(typeof param === 'string') {
// ok
}
}
typeof类型守卫只有两种形式能被识别:typeof param === 'typename'和typeof param !== 'typename'typename必须是'number'/'string'/'boolean'/'symbol'
(ts不会阻止你和其他字符串比较,但不会将其识别为类型守卫)
instanceof类型守卫
与typeof类似
keyof 索引类型查询操作符
对于类型T,keyof T的结果是该类型上所有公共属性名的联合
interface A {
name: string
age: number
}
// T1的类型实则是 “name” | “age”
type T1 = keyof A
class B {
private name: string
public age: number
}
// T2的类型被约束为 "age"
// 因为name不是公有属性,不能被keyof获取到
type T2 = keyof B
interface C {
gender: string
}
// T3的类型实则是 "name" | "age" | { gender: string }
type T3 = keyof A | C
️:keyof any 的结果为string | number | symbol
ts2.8 作用于 交叉类型 的 keyof 会被转换成作用于交叉成员的 keyof 的联合,也就是说 keyof (A & B) 会被转换为 keyof A | keyof B
typeof操作符
ts对typeof操作符做了扩展,可以用来获取一个变量或对象的类型
type Person = {
name: string
age: number
}
const man: Person = {
name: 'poem',
age: 20
}
// type Human = {
// name: string
// age: number
// }
type Human = typeof man
这很有用。一个具体的情况是:使用import * as options from '...'将一个模块全部导入,此时 options 默认为 any 类型,当我们用 options[key] 的方式访问对象属性时会报错,所以需要定义一个用来判断属性 key 是否存在于指定对象中的类型守卫:
export function isValidKey(key: string | number | symbol, obj: object): key is keyof typeof obj {
return key in obj
}
T[K]索引访问操作符
interface T {
name: string
age: number
}
interface T2 {
name: string
gender: string
}
interface T3 {
name: string
}
// string
type K1 = T['name']
// string | number
type K2 = T['name' | 'age']
// any, 类型T上不存在属性age111
type K3 = T['name' | 'age111']
// string | number
type K4 = T[keyof T]
// any, 类型T上不存在属性gender
type K5 = T[keyof T2]
// string
type K6 = T[keyof T3]
T[keyof T2]的方式获取到的是T中的key且同时存在于K时的类型组成的联合类型
如果[]中的key有不存在于T中的,则是any类型,且报错
映射类型
映射类型-从旧类型中创建新类型的方式
需要使用关键字in
interface A {
name: string
age: number
}
// 类型A中的key全部转成只读的
type ReadonlyA<T> = {
// 映射类型
readonly [K in keyof T]: T[K]
}
let a: ReadonlyA<A> = {
name: 'poem',
age: 20
}
// 报错 name是只读属性
a.name = 'change'
️:只能用 type 做类型映射,不能用 interface
最简单的映射类型:
type Keys = 'key1' | 'key2'
type Flags = {
[K in Keys]: boolean
}
类型变量K,会依次绑定到每个属性Keys包含了要迭代的属性名的集合:冒号后面写属性的结果类型
extends条件类型
以一个条件表达式进行类型关系检查,使用关键字extends配合三元运算符T extends U ? X : Y
若T是U的子类型则类型为X,否则类型为Y
若无法确定T是否是U的子类型,则类型为X | Y
分布式条件类型
当被检测类型是一个联合类型时,该条件类型就是分布式条件类型
当extends前面的参数是泛型且是联合类型时,才会被分解。然后进行条件判断,最后结果组成新的联合类型
// type A = 2 不会分解
type A = 'x' | 'y' extends 'x' ? 1 : 2
// type newB = 1 | 2
type B<T> = T extends 'x' ? 1 : 2
type newB = B<'x' | 'y'>
阻止extends对联合类型的分发特性
如果不想被分发,可以通过简单的元组类型包裹:
// type newB = 2
type B<T> = [T] extends ['x'] ? 1 : 2
type newB = B<'x' | 'y'>
边栏推荐
猜你喜欢
随机推荐
高等数学---第十章无穷级数---常数项级数
cocos creater 3.x 插件安装方法
货比四家 version tb1.63
高数---级数
B628芯片电路图,B628升压IC的PCB布局PCB
【美丽天天秒】链动2+1模式开发
Shell编程案例
实现博客营销有哪些技巧
动态接口比例性能测试实践
CodeTON Round 2 (Div. 1 + Div. 2, Rated, Prizes!) A-E
借助Web3盘活日本优质IP:UneMeta 与 OpenSea 的差异化竞争
2021年数据泄露成本报告解读
Share 14 JS functions you must know
WPF 实现柱形统计图
常见荧光染料修饰多种基团及其激发和 发射波长数据一览数据
Discuz新闻资讯GBK模板
Mock模拟数据,并发起get,post请求(保姆级教程,一定能成功)
借助kubekey极速安装Kubernetes
ImportError: /lib/libgdal.so.26: undefined symbol: sqlite3_column_table_name
走进通信:为什么4G信号满格,却上不了网呢









