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

156 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 1 天

深入对象语法

对象可以用 type 和 interface 来描述

// 表达一种含有一些某些属性的对象
// 以下 Person1/2/3 都等价
type Person1 = {
  name: string
}

// 以下 Person2 所有的类型都是这个 Person1 的子集 
type Person2 = 
  | { 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, age: string }
  | { name: string, age: number }
  // 以下省略一万行....
  
interface Person3 {
  name: string
}  

索引签名 Index Signature

// 这里的 k 不一定要叫 k, 满足变量名要求即可
type Hash = {
  [k: string]: unknown
  length: number
}

type List = {
  [k: number]: unknown
  length: number
}

映射类型 Mapped Type (多用于范型)

// in 主要是用于范型
type Hash = {
  [k in string]: unknown
}


type List = {
  [k in number]: unknown
}

Screen Shot 2022-10-09 at 12.44.39 PM.png 写成以上写法会产生以下报错 Screen Shot 2022-10-09 at 12.44.05 PM.png

问号表示可选

interface InputProps {
  defaultValue?: string
  value?: string  
  //value1: string | undefined
  onChange?: () => void
}


// 这里的可选是不是等价于 它可以是 undefined ?
const props: InputProps = {
  value: undefined,
  // value1: undefined,
  defaultValue: 'hi',
  onChange: ()=> 1
}

// 可以的,也就是说 ? 是可以給它赋值为 undefined 
// 但是用 undefined 代表 ? 它表达的意思就不一样

Screen Shot 2022-10-09 at 3.21.07 PM.png

Screen Shot 2022-10-09 at 3.20.34 PM.png

所以为了避免麻烦,优先使用 ?

readonly 表示只读,不能写

interface User {
  readonly id: number
  readonly name: string
  readonly scores: number[]
  age?: number
}

const u: User = {
  id: 1,
  name: 'hone',
  scores: [87, 65]
}

u.id = 2
u.scores = [100, 100]

// 改 scores 对应的下标 不报错
// 这说明 这个readonly 只会管 scores 这一层,它的下一层不管
u.scores[0] = 100

Screen Shot 2022-10-09 at 3.29.40 PM.png

Screen Shot 2022-10-09 at 3.33.54 PM.png

readonly 可以保证它后面的属性不可写,但是并不保证这个属性里面的属性不可写。

函数就像对象一样

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

  1. type 或 interface
// 如果想在函数上加点东西,就用以下这种写法
type F = {
  (a: number, b: number): number
  // 比如说这个函数上有一个属性
  count: number
}

// F 等价于 F1
// 如果只是想声明一个函数就用以下这种写法
type F1 = (a: number, b: number) => number

const f: F = (x, y) => {
  return x + y
}
f.count = 1
interface F {
  (a: number, b: number): number
  count: number
}

// F 等价于 F1

type F1 = (a: number, b: number) => number

const f: F = (x, y) => {
  return x + y
}
f.count = 1
  1. 索引签名 和 映射类型

  2. 问号表示可选

interface F {
  (a: number, b: number): number
  count?: number
}

const f: F = (x, y) => {
  return x + y
}
// 这个 count 就是可有可无的
  1. readonly 表示只读
interface F {
  (a: number, b: number): number
  readonly count?: number
}

const f: F = (x, y) => {
  return x + y
}

声明函数的4种方式

第一种:先写类型再赋值

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

第二种:先实现箭头函数,再获取类型

const f2 = (a: number, b: number): number => {
  return a + b
}

type F2 = typeof f2

第三种:先实现普通函数,再获取类型

function f3(this: unknown, a: number, b: number): number {
  return a + b
}

type F3 = typeof f3

第四种:先实现匿名普通函数,再获取类型

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

第五种: 写的很少,除非写库

const f5 = new Function('a', 'b', 'return a + b')

type F5 = typeof f5

暂不讨论 生成器和异步函数

类型谓词

// 自带的类型比较好收窄
function f1(a: string | string[]) {
  // 这个时候我们在用 a 的时候就需要做类型收窄
  if (a instanceof Array) {
    a  // Array
  } else {
    a // string
  }
}
type Person = {
  name: string
}
type Animal = {}

// 如果还有其它的类型,就不太好收窄了
// function f1(a: Person | Animal) {}

// 那怎么办呢?
// 我们就需要借助 isPerson
// 这里的 boolean 不写也会自动补全的
function isPerson(x: Person | Animal): boolean {
  if ('name' in x) {
    return true
  } else {
    return false
  }
}

// 这样的话我们就可以使用
function f1(a: Person | Animal) {
  if (isPerson(a)) {
    a // 能不能收窄?没有收窄
  }
}

Screen Shot 2022-10-09 at 4.27.25 PM.png

问题在于 isPerson 这个函数和 Person 这个类型,没有任何关系。

所以得要想办法让这两个东西,建立关联,那么如何建立关联?

// 把 boolean 写成 Person 
function isPerson(x: Person | Animal): Person {
  if ('name' in x) {
    return true
  } else {
    return false
  }
}

但是写上 Person 就有问题,说明返回的是一个 Person Screen Shot 2022-10-09 at 4.47.40 PM.png

写成 is Person 也不行,因为你没有告诉我,哪个变量是 Person Screen Shot 2022-10-09 at 5.08.18 PM.png

所以就把这个变量名写到后面来

// x is Person  的意思是:我的返回值确实是一个 boolean 
// 但是这个 boolean 是关系到 x 是不是 Person
function isPerson(x: Person | Animal): x is Person {
  return 'name' in x
}

这个所谓的收窄只是在写代码的时候,做代码提示的时候要收窄,运行的时候只需要有这个属性就行了,不管收窄的(运行的时候只管逻辑不管类型)。

以上代码为何不能用箭头函数呢?因为箭头函数不能给名字。

const isPerson2 =(x: Person | Animal) => {
  return 'name' in x
}

发现并没有收窄 Screen Shot 2022-10-09 at 5.24.29 PM.png

因为没有关键的 is Person

但是问题在于,isPerson 有两个写法

写在右边没有问题 Screen Shot 2022-10-09 at 5.28.05 PM.png

写在左边为什么会报错?
Screen Shot 2022-10-09 at 5.32.22 PM.png

type A = (x: Person | Animal) => x is Person 
const isPerson2: A = (x)=> 'name' in x
// ()=>x is Person         ()=>boolean
// boolean 是不能赋值给 x is Person 的

// 除非写成以下写法
// const isPerson2: A = (x):x is Person=> 'name' in x
// 这里 x is Person 写了两遍,那还不如全写在右边

问题在于:boolean 是不能变成 is 的,TS 以为 (x)=> 'name' in x 这个是 boolean, 但是 boolean 和 is 不是等价的,所以就错了。

所以用类型谓词尽量用 function, 少用箭头函数,就不会存在纠结了。

参数相关语法

可选参数

// useCapture 如果不传它就是 undefined
function addEventListener(
  eventType: string, fn: unknown, useCapture?: boolean
) {
  console.log(eventType, fn, useCapture)
}

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

参数默认值

// 写成 useCapture = false 表示·参数默认值是 false
function addEventListener(
  eventType: string, fn: unknown, useCapture = false
) {
  console.log(eventType, fn, useCapture)
}

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

参数也是函数

Screen Shot 2022-10-09 at 6.44.19 PM.png

// 在 TS 里是不分是不是箭头函数的,只分能不能加 this
// (this: HTMLElement, e: Event) => void 这个代表它是一个函数,不能代表它是一个箭头函数
function addEventListener(
  eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture?: boolean
) {
  // 伪代码
  const element = {} as HTMLElement
  const event = {} as Event
  // 以上先构造所有需要的对象然后传给下面
  fn.call(element, event)
  console.log(eventType, fn, useCapture)
}

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

函数柯里化

返回值也是函数

const add = (a: number, b: number) => a + b

const createAdd => (a: number) => (b: number) => a + b

createAdd(6)(14)  // 20
// 把类型和值分开写
type CreateAdd = (x: number) => (y: number) => number
const createAdd: CreateAdd = a => b => a + b