深入函数:声明、参数、返回值

深入对象语法

描述对象的属性

使用接口描述对象

1
2
3
4
5
6
7
8
type Person = {
name: string
}

// 或者
interface Person {
name: string
}

Person 的子集为

1
2
3
4
5
6
7
8
9
10
type Children =
| { name: string, age: string }
| { name: string, age: number }
| { name: string, age: boolean }
| { name: string, age: null }
| { name: string, age: undefined }
| { name: string, age: object }
| { name: string, gender: string }
| { name: string, gender: number }
// 以下省略一万行

索引签名(Index Signature)

1
2
3
4
5
6
7
8
type Hash = {
[k: string]: unknown
length: number
}
type List = {
[k: number]: unknown
length: number
}

映射类型(Mapped Type)

1
2
3
4
5
6
type Hash = {
[k in string]: unknown
}
type List = {
[k in number]: unknown
}

映射类型多用于泛型

1
2
3
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};

需要注意的是,如果使用了映射类型,对象中就不能再有其他属性了,这点与索引签名有所不同

1
2
3
4
5
type Hash = {
[k in string]: unknown
length: number
// ^-- A mapped type may not declare properties or methods.
}

可选属性

可选属性的含义是该属性可以不存在

1
2
3
4
5
interface InputProps {
defaultValue?: string;
value?: boolean;
onChange?: () => void;
}

value?: boolean 可近似的理解成 value: boolean | undefined,但是在声明对象属性时,value? 可以不写,而 value 必须写

只读属性

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性

1
2
3
4
5
6
7
8
9
10
11
interface User {
readonly id: number;
readonly name: string;
readonly scores: number[];
age?: number;
}
const u: User = {
id: 1,
name: 'ClariS',
scores: [87, 65],
};

使用 readonly 定义的属性初始化后不能被重新赋值

1
2
3
4
u.id = 2;
// ^-- Cannot assign to 'id' because it is a read-only property.ts
u.scores = [100, 65]
// ^-- Cannot assign to 'scores' because it is a read-only property.ts

但是并没有对 scores 内部的属性做只读约束(只读约束只在第一层)

1
2
u.scores[0] = 100;
// 不报错

深入函数语法

对象的语法全都适用于函数

1
2
3
4
5
6
7
type F = {
(a: number, b: number): number;
readonly count?: number[];
};

const fn: F = (x, y) => x + y;
fn.count[0] = 1;

声明函数的四种方式

先写类型再赋值

1
2
type F1 = (a: number, b: number) => number;
const f1: F1 = (a, b) => a + b;

箭头函数

1
2
3
4
const f2 = (a: number, b: number): number => {
return a + b;
};
type F2 = typeof f2;

普通函数

1
2
3
4
function f3(this: unknown, a: number, b: number): number {
return a + b;
}
type F3 = typeof f3;

匿名普通函数

1
2
3
4
const f4 = function (this: unknown, a: number, b: number): number {
return a + b;
};
type F4 = typeof f4;

构造函数

1
2
const f5 = new Function('a', 'b', 'return a + b');
type F5 = typeof f5;

几乎没人会这么写吧

类型谓词

之前在联合类型中讲过,可以使用类型谓词 is 来收窄类型,当时只简单提了一下不推荐使用箭头函数,这里说一下为什么

1
2
3
4
5
6
7
8
9
10
11
12
type Person = { name: string }
type Animal = {}

function isPerson(x: Person | Animal): x is Person {
return 'name' in x
}

function fn(a: Person | Animal) {
if (isPerson(a)) {
a // Person
}
}

可以使用箭头函数实现 isPerson

1
const isPerson = (x: Person | Animal): x is Person => 'name' in x

但需要注意的是,如果使用的是箭头函数,不能先写类型再赋值

1
2
3
type A = (x: Person | Animal) => x is Person
const isPerson: A = x => 'name' in x
// ^--- Type '(x: Person | Animal) => boolean' is not assignable to type 'A'

在右边加上 x is Person 就不报错了

1
2
type A = (x: Person | Animal) => x is Person
const isPerson: A = (x): x is Person => 'name' in x

参数相关语法

可选参数

1
2
3
4
5
6
7
8
9
function addEventListener(eventType: string, fn: unknown, useCapture?: boolean) {
if (useCapture === undefined) {
useCapture = false
}
// 浏览器实现
console.log(eventType, fn, useCapture)
}

addEventListener('click', () => 1)

参数默认值

1
2
3
4
5
6
function addEventListener(eventType: string, fn: unknown, useCapture = false) {
// 浏览器实现
console.log(eventType, fn, useCapture)
}

addEventListener('click', () => 1)

参数也可以是函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function addEventListener(
eventType: string,
fn: (this: HTMLElement, e: Event) => void,
useCapture = false
) {
// 浏览器实现,这里只提供伪代码
const element = {} as HTMLElement;
const event = {} as Event;
fn.call(element, event);
console.log(eventType, fn, useCapture);
}

addEventListener('click', () => 1);

返回值也可以是函数

1
2
3
4
5
6
const add = (a: number, b: number) => a + b
type CreateAdd = (x: number) => (y: number) => number
const createAdd: CreateAdd = a => b => a + b

add(6, 14)
createAdd(6)(14)

(●'◡'●)ノ♥