高级类型
class类
TS中的class
,不仅提供了原生class的语法功能,也作为一种类型存在。
基本使用:
// 定义类
class Person {}
// 使用类
const p = new Person()
作为类型使用:
class Person {
age: number
gender = '男' // 简化写法
// gender: string = '男' // 完整写法
}
构造函数
class Person {
private age: number;
private gender: string;
constructor(age: number, gender: string) {
this.age = age;
this.gender = gender;
}
}
const p = new Person(20, '男')
实例方法
class Point {
private x = 1;
private y = 2;
scale(n: number) {
this.x *= n;
this.y *= n;
}
}
const p = new Point()
p.scale(10)
类的继承
类的继承有两种方式:extends
(继承父类)和implements
(实现接口)。
class Animal {
move() {
console.log("走两步");
}
}
class Dog extends Animal {
name = "二哈";
bark() {
console.log("旺旺!");
}
}
const d = new Dog();
d.move();
itnterface Singable {
name: string
sing(): void
}
// 子类实现父类接口意味着,Person子类中必须定义 Singable 父类中所有的方法和属性
class Person implements Singable {
name: '小明'
sing() {
console.log('爱是不是不开口才珍贵~')
}
}
类成员可见性
可见性修饰符有三个:
修饰符 | 作用 |
---|---|
public | 共有的 |
protected | 受保护的(自己或子类内部可以使用,外部实例无法调用) |
private | 私有的(只在自己类内可以使用) |
public
:
// public 是默认可见性,一般情况下可以省略不写
class Animal {
public move() {
console.log('走两步')
}
}
protected
:
class Animal {
protected move() {
console.log('走一走')
}
run() {
this.move()
}
}
const a = new Animal()
// a.move() // 父类实例方法不能调用
class Dog extends Animal {
eat() {
this.move() // 子类的其他方法中,可以调用
}
}
const d = new Dog()
// d.move() // 类实例方法不能调用
pritive
:
class Animal {
private run() {
console.log('哈喽啊')
}
protected move() {
this.run() // 父类的 protected 方法中可以调用
}
run() {
this.run() // 父类的 public 方法中可以调用
}
}
const a = new Animal()
// a.run() // 父类的实例方法不能调用
class Dog extends Animal {
eat() {
a.run() // 子类的普通方法中不能调用
}
}
const d = new Dog()
// d.run() // 子类的实例方法不能调用
交叉类型
交叉类型使用 &
关键字声明,功能类似于接口继承(extends),用于组合多个类型为一个类型。
interface Person {
name: string
}
interface Concat {
phone: string
}
// 交叉类型
type PersonDetail = Person & Concat
let obj: PersonDetail = {
name: '小明',
phone: '123456'
}
交叉类型和继承对比
相同点:都可以实现对象类型的组合;
不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
TypeScriptinterface A { fn: (value: string) => {} } // 继承 interface B extends A { // 注意:此时的 B 会报错(不能将 string 类型分配给 number) fn: (value: number) => {} }
TypeScriptinterface A { fn: (value: string) => {} } interface B { fn: (value: number) => {} } // 交叉类型 type C = A & B // value 既可以是 string 类型,也可以是 number 类型 let obj: C = { fn(value: string | number) { return '' } }
泛型
当想传递任意类型,还要类型检查,就可以使用泛型。
// 定义泛型函数
function getId<T>(value: T): T {
return value
}
// 调用泛型函数
const num = getId<number>(123)
const str = getId<string>("admin")
泛型约束
泛型函数的类型变量 T 可以代表任意类型,这就导致无法访问某些属性。
function getId<T>(value: T): T {
// 此时的 length 就会报错!因为不确定 Type 到底是什么类型,如 number 没有 length 属性
const length = value.length
return length
}
解决方案:
指定更加具体的类型
添加约束
// 指定为 T 类型的数组,因为数组有长度
function getId<T>(value: T[]): T[] {
value.length
return value
}
interface ILength { length: number }
// T类型必须满足 ILength 指定的要求
function getId<T extends ILength>(value: T): T {
value.length
return value
}
keyof获取泛型参数
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
keyof
关键字接受一个对象类型,生成其键名称的联合类型。
// keyof T 实际上获取的是 person 对象所有键的联合类型,也就是 'name' | 'age'
function getProp<T, key extends keyof T>(obj: T, key: key) {
return obj[key]
}
class Person {
name: "admin"
age: 20
}
const person = new Person()
// 类型变量 key 受 Type 约束,即 key 只能是 Type 所有键中的任意一个,或者只能访问对象中存在的属性
const value1 = getProp(person, "name")
const value2 = getProp(person, "age")
泛型接口
在接口名称的后面添加 <类型变量>
,这个接口就变成了泛型接口。
interface IdFunc<T> {
id: (value: T) => T
ids: () => T[]
}
let obj: IdFunc<number> = {
id: (value) => value, // id 方法的参数和返回值都是 number 类型
ids: () => [1, 2, 3] // ids 方法无参数,返回值是 number[]
}
泛型类
class也可以配合泛型来使用。
class GenericNumber<T> {
defaultValue: T
add: (x: T, y: T) => T
}
// 实例化的时候,明确指定实例的类型
const myNum = new GenericNumber<number>()
myNum.defaultValue = 10
泛型工具类型
TS 中内置了一些常用的工具类型,来简化 TS 中的一些操作。
Partial<T>
Partial<T>
用来构造一个类型,将 T 的所有属性设置为 可选。
interface Props {
id: number
hobby: string[]
}
// 把 Props 接口中定义的属性全部变为可选
type partialProps = Partial<Props>
Readonly<T>
Readonly<T>
用来构造一个类型,将 T 的所有属性都设置为 只读。
interface Props {
id: number
hobby: string[]
}
// 把 Props 接口中定义的属性变为只读
type partialProps = Readonly<Props>
Pink<T>
Pink<T>
可以从 T 中选择一组属性来构造新的类型。
interface Props {
id: number
title: string
hobby: string[]
}
// PickProps 就有了 Props 中 id、title 两个属性
type PickProps = Pick<Props, 'id' | 'title'>
Record<keys, T>
Record<keys, T>
构造一个对象类型,属性键为 keys,属性类型为 T。
type RecordObj = Record<'a' | 'b' | 'c', string[]>
// 上面代码等价于
type RecordObj = {
a: string[]
b: string[]
c: string[]
}
let obj: RecordObj = {
a: ['a'],
b: ['b'],
c: ['c']
}
索引签名类型
当我们无法确定对象中有哪些属性时,就可以使用索引签名类型了。
对象:
interface anyObject {
// [key: string] 来约束该接口中,只要是 string 类型的属性名称,都可以出现在对象中。
[key: string]: number
}
let obj: anyObject = {
a: 1,
b: 2
}
数组:
interface array<T> {
[index: number]: T
}
let arr: array<number> = [1, 2, 3]
let arr1: array<string> = ['1', '2', '3']
映射类型
映射类型是基于旧类型,创建一个新类型(对象类型)。
映射类型是基于索引签名类型的,所以,该语法中也使用了索引签名类型 []
的用法。
注意
映射类型只能在类型别名(Type)中使用,不能在接口(interface)中使用。
// 如果 PropKeys 的属性值很多时,一个一个定义就显得乏力
type Type1 = {
x: number;
y: number;
z: number;
}
type PropKeys = 'x' | 'y' | 'z'
// 无论 PropKeys 的属性值有多少个,都会自动生成
type Type2 = { [key in PropKeys]: number }
keyof
映射类型既可以根据联合类型创建新类型,也可以根据对象类型创建新类型。
type Props = {
a: string
b: number
c: boolean
}
// keyof Props 获取到对象类型 Props 中所有键的联合类型,即 'a' | 'b' | 'c'
// key in 表示 key 可以是 Props 中所有的键名称中的任意一个
// Props[key] 表示获取每个键对应的类型
type newProps = {
[key in keyof Props]: Props[key]
}
索引查询类型
使用T[p]
的方式在 TS 中实现根据索引查询类型。
type Props = {
a: number
b: string
c: boolean
}
type newProps = Props["a"] // number
type newProps1 = Props["a" | "b"] // number | string
// 查询全部
type newProps2 = Props[keyof Props] // number | string | boolean