ts高级类型
类型的且运算
interface A {
name: string,
age: number
}
interface B {
name: string,
garde: number
}
const c: A & B = {
name: 'lifa',
age: 18,
garde: 100
}
上面的代码c变量的类型为A和B,也就是要同时满足A和B里所有的字段定义,name、age和garde少一个都会报错
在react中使用且运算
场景:我们声明一个layout函数组件
const Layout: React.FunctionComponent = () => {
return (
React.createElement('div', null, 'hi')
)
}
然后我们需要给Layout加一个Header属性,但是我们的Layout上并没有Header属性,如果直接声明会报错
- 方法1:
这时候我们就可以使用我们的且运算,给Layout再添加一个Header也指定为函数组件类型,就相当于指定一个接口,接口里面有一个Header属性,然后赋值给变量一个有Header属性的对象,所以可以直接用我们的变量点属性,也就是Layout.Header
const Layout: React.FunctionComponent & { Header: React.FunctionComponent} = () => {
return (
React.createElement('div', null, 'hi')
)
}
Layout.Header = () => {
return (
React.createElement('div', null, 'hi')
)
}
方法2:使用继承
interface Layout2 extends React.FunctionComponent {
Header: React.FunctionComponent
}
const Layout2: Layout2 = () => {
return (
React.createElement('div', null, 'hi')
)
}
Layout2.Header = () => {
return (
React.createElement('div', null, 'hi')
)
}
从上面的代码可以看出来ts可以声明接口名和变量名是同一个名字,它会自动给你匹配
类型的或运算
interface A {
name: string,
age: number
}
interface B {
name: string,
grade: number
}
const c: A | B = {
name: 'lifa',
age: 18
}
function add(a: string | number, b: string | number) {
return a + b
}
类型别名type
对于上面的layout我们指定它的类型为:React.FunctionComponent & { Header: React.FunctionComponent}
,这样看起来代码非常长,我们可以将它们单独拿出来声明一个type
,赋值给type
type Layout = React.FunctionComponent & { Header: React.FunctionComponent}
type和interface的区别
type是给一个已知的类型取了一个别名,而interface是声明了一个新的类型,在我们不知道该用type还是interface的时候最好用interface
// 将已知的string类型命名为Name
type Name = string
const name1: Name = '小小四'
字面量类型
所谓的字面量类型就是不直接声明类型,而使用一个值来代替这个类型,比如1就可以代替number,'1'就可以代替string,ts会自动根据你的值匹配对应的类型
interface Women {
name: 'boduo' | 'cangjing' | 'zhaoliying'
}
const wife: Women = {
name: 'zhaoliying'
}
上面我们直接通过字面量来指定string类型,wife的name值只能是三个中的一个
更多例子:
type Dir = 'east' | 'west' | 'north' | 'south'
const dir: Dir = 'east'
type B = true | false | 6 | 'west'
const c: B = true
interface Course {
name?: string,
// 等价于
name1: string | undefined
}
this类型
- 没有继承关系的类
当我们直接对一个基础的类实例化的时候,它里面的this会指向这个类,所以当返回this的时候,还可以继续调用这个类的方法。
class Calc {
public value: number
constructor(n: number) {
this.value = n
}
add(n: number) {
this.value += n
return this
}
multiple(n: number) {
this.value *= n
return this
}
}
const c = new Calc(1)
c.add(2).multiple(3)
- 有继承关系的类
class Calc {
public value: number
constructor(n: number) {
this.value = n
}
add(n: number) {
this.value += n
return this
}
multiple(n: number) {
this.value *= n
return this
}
}
class BiggerCalc extends Calc {
sin() {
this.value = Math.sin(this.value)
return this
}
}
const c = new BiggerCalc(1)
c.add(2).multiple(3).sin()
当我们以子类生成一个实例对象的时候,this就会指向我们的子类,它既可以调用父类的方法,也可以调用自己的方法
总结:
this既可以指向你的父类又可以指向你的子类,具有多种状态所以this是多态的
索引类型
- 在我们不确定我们的参数的个数的情况下,我们可以直接通过指定属性的key为string,然后属性的value为任意类型,这样我们就可以在后期在原有的接口属性的基础上添加或修改了
比如:
const calender = (options: CalenderOptions) => {
}
interface CalenderOptions {
[k: string]: any
}
calender({
time: Date.now,
view: 'year'
})
我们的CalenderOptions
中[k: string]: any
,这样我们的calender在调用的时候里面的属性就可以随意的添加了
- 检索属性的key是否是对应对象里的key
function pluck<T, K extends keyof T>(object: T, kes: Array<K>) {
}
pluck({ name: 'lifa', age: 18, habit: '女' }, ['name', 'age'])
代码解释
T: { name: string, age: number, habit: string }
keyof T: 'name' | 'age' | 'habit' (也就是T的键名)
因为K集成keyof T所以 K也是'name' | 'age' | 'habit'
所以pluck的第二个参数必须是第一个参数里面的键名,如果不是就会报错
- 复杂的返回值类型
function pluck<T, K extends keyof T>(object: T, keys: Array<K>): T[K][] {
return keys.map(key => object[key])
}
pluck({ name: 'lifa', age: 18 }, ['name', 'age'])
上面的T[K][]直接看的话我们很难理解,我们可以逆推,当我们调用pluck的时候拿到的返回值是['lifa', 18]
(1).
['lifa', 18]
换成类型就是Array<string | number>
(2).string
也就是T[name]
,number
也就是T[age]
,就会变成Array<T[name] | T[age]>
(因为上面的T
就是{name: string, age: number}
)
(3).name
和age
也就是我们的K
,所以可以改写成Array<T[K] | T[K]>
,前后都是T[K]
就可以写成一个Array<T[K]>
(4).Array<T[K]>
等价于T[K][]
Readonly和Partial
- Readonly
有两个属性名和类型都相同的接口,只不其中一个里面的属性都是只读的,按照之前我们的写法会像下面这样
interface Person {
name: string,
age: number
}
interface ReadonlyPerson {
readonly name: string,
readonly age: number
}
但这样写起来代码明显很冗余,我们还可以这样写
interface Person {
name: string,
age: number
}
type ReadonlyPerson2 = Readonly<Person>
我们来看一下Readonly的源码
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
这里的in和前面的extends的区别是in是包含了数组里的每一项,也就是name和age都必须包含,而extends可以是其中的一项或多项,
type Readonly<{name: string, age: number}> = {
// 类型转换过程
(1). readonly name: T[name];
readonly age: T[age]
(2). readonly name: string;
readonly age: number
};
- Partial
指定所有的属性都是可选的
interface Person2 {
name?: string,
age?: number
}
// 等价于
type Person3 = Partial<Person>
可辨识联合
- 使用场景:
interface Props {
acton: 'create'|'update';
id?: number;
name: string;
}
我们有一个借口Props里面有三个属性,其中action是联合的可以取create或者update,但是id是可选的,如果是create是没有id的,只有update的情况下才有id,但是很明显上面的接口不满足我们的需求,所以我们需要写成下面这样
type Props = {
action: 'create',
name: string
} | {
action: 'update',
name: string,
id: number
}
声明使用:
const p1: Props = {
action: 'create'
}
const p2: Props = {
action: 'update'
}
const p3: Props = {
action: 'create',
name: 'TS入门'
}
const p4: Props = {
action: 'update',
name: 'TS入门'
}
const p5: Props = {
action: 'update',
name: 'Ts入门',
id: 1
}
上面只有p3
和p5
是满足条件的;
问题:如果我们声明一个接口满足Props
类型,直接打印出它的id
会报错;
原因:Props
里不一定有id
属性
解决方法:通过他们共有的一个action
属性来判断id
,如果action
等于update
就有id
,否则没有
function fn(a: Props) {
if (a.action === 'update') {
console.log(a.id)
} else {
console.log(a.name)
}
}
- 可辨识类型的前提
(1). 有一个共有的字段,比如上面的action
(2). 共有字段是可穷举的(值是有限制的,也就是说有固定的几个值;而不是无穷个,比如action: string,这就有无穷个值了,就不符合) - Redux的Action中使用可辨识类型
type Action2 = {
type: 'ADD',
payload: number
} | {
type: 'ADD_STRING',
payload: string
} | {
type: 'ADD_DATE',
payload: Date
}
function reducer(state: any, action: Action2) {
switch(action.type) {
case 'ADD':
action.payload = 1
break
case 'ADD_STRING':
action.payload = '1'
break
}
}