TypeScript基本使用

292 阅读10分钟

1.安装编译TS工具包

npm i -g typescript 或者 yarn global add typescript。

JS 已有类型。

原始类型:number/string/boolean/null/undefined/symbol/bigint。

对象类型:object(包括,数组、对象、函数等对象)。

TS 新增类型

联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any 等。

let age: number = 18
let myName: string = 'Ifer'
let isLoading: boolean = false
// 数组两种写法
let numbers: number[] = [1, 3, 5]
let strings: Array<string> = ['a', 'b', 'c']

2.联合类型

|(竖线)在 TS 中叫做联合类型,即由两个或多个其他类型组成的类型,表示可以是这些类型中的一种。

// 联合类型
let arr: (number | string)[] = [1, 'abc', 2]
let timer: number | null = null
timer = setInterval(() => {}, 1000)

3.类型别名

类型别名作用:为任意类型起别名,别名甚至可以是中文。

使用 type 关键字来创建自定义类型。

type s = string
const myName: s = 'ifer'

type 字符串类型 = string
const myAddress: 字符串类型 = '河南老乡~'

type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]

4.函数类型

函数的类型实际上指的是:函数参数 和 返回值 的类型

#1 单独指定参数 , 返回值类型

// 函数声明
function add(num1: number, num2: number): number {
    return num1 + num2
}

// 箭头函数
const add = (num1: number, num2: number): number => {
  return num1 + num2
}

#2 同时指定参数 , 返回值的类型

// 可以通过类似箭头函数形式的语法来为函数添加类型,注意这种形式只适用于函数表达式。
type AddFn = (num1: number, num2: number) => number

const add: AddFn = (num1, num2) => {
    return num1 + num2
}

5.void类型

注意:在没有开始 strictNullChecks 模式的情况下,可以把 null 和 undefined 赋值给任意类型

如何开启:通过 tsc --init 生成配置文件,默认就会开启 strictNullChecks

// 注意:在没有开始 strictNullChecks 模式的情况下,可以把 null 和 undefined 赋值给任意类型
// 如何开启:通过 tsc --init 生成配置文件,默认就会开启 strictNullChecks
// let temp: void = null // ok
let temp: void = undefined // ok

如果函数没有返回值 , 那么函数返回值为void

function greet(name: string): void {
    console.log('Hello', name)
    // return undefined // 默认有这么一句
}

如果一个函数明确了返回类型是 undefined,则必须显示的 return undefined

const add = (): undefined => {
    return undefined
}

6.可选参数

可选参数语法:在可传可不传的参数名称后面添加 ?(问号)

使用函数实现某个功能时,参数可以传也可以不传,这种情况下,在给函数参数指定类型时,就用到可选参数了。

注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数

// startend 可传可不传,传就传 number 类型
function mySlice(start?: number, end?: number): void {
    console.log('起始索引:', start, '结束索引:', end)
}

7.参数默认值

通过赋值符号(=)可以给参数执行默认值,注意:参数默认值和可选参数互斥的,只能指定其中一种。

// 错误
function mySlice(start: number = 0, end?: number = 0) {}

// 可选参数
function mySlice(start: number = 0, end?: number) {}
// 默认值
function mySlice(start: number = 0, end: number = 0) {}

8.对象类型

// 基本使用
const person: object = {}

// 另一种使用方式
let person: {} = {}  // 左边{}为字面量类型

// 要求必须指定 string 类型的 name 属性,左右两边数量保持一致
const person: { name: string } = {
    name: '同学',
}

const obj = {
    name: '同学',
    age: 18,
}
// 右边是变量,在满足左边声明的前提下(右边内容可以比左边多)
const person: { name: string } = obj

// 字符串比较特殊,满足左边的类型要求即可
const str: { length: number } = 'hello'

// 描述对象中方法的类型。
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
// 单独制定函数的参数和返回值
// const person: { name: string; add(n1: number, n2: number): number } = {}
// 可以统一指定函数的参数和返回值
const person: { name: string; add: (n1: number, n2: number) => number } = {
    name: '同学',
    add(n1, n2) {
        return n1 + n2
    },
}

// 通过换行来分隔多个属性类型,去掉 `;
const person: {
    name: string
    add(n1: number, n2: number): number
} = {
    name: '同学',
    add(n1, n2) {
        return n1 + n2
    },
}

// 结合类型别名
type Person = {
    name: string
    add(n1: number, n2: number): number
}
const person: Person = {
    name: '同学',
    add(n1, n2) {
        return n1 + n2
    },
}

1.对象可选类型

比如,我们在使用 axios({ ... }) 时,如果发送 GET 请求,method 属性就可以省略。

type Config = {
    url: string
    method?: string
}

function myAxios(config: Config) {
    console.log(config)
}

9.接口

当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。

interface IStudent {
    name: string
    gender: string
    study(): void
}

const stu: IStudent = {
    name: 'xxx',
    gender: 'man',
    study() {
        console.log('学学学')
    },
}

1.接口继承

interface Point2D {
    x: number
    y: number
}
// 使用 `extends`(继承)关键字实现了接口 Point3D 继承 Point2D
// 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)
interface Point3D extends Point2D {
    z: number
}

2.interface vs type

相同点 :都可以描述对象或者函数

// interface 描述对象
interface IPerson {
    name: string
    age: number
}
const p: IPerson = { name: 'ifer', age: 18 }

// interface 描述函数
interface ISetPerson {
    (name: string, age: number): void
}
const setPerson: ISetPerson = (name, age) => {}

setPerson('ifer', 18)
// type 描述对象
type TPerson = {
    name: string
    age: number
}
const p: TPerson = { name: 'ifer', age: 18 }

// type 描述函数
type TSetPerson = {
    (name: string, age: number): void
}
const setPerson: TSetPerson = (name, age) => {}
setPerson('ifer', 18)

都允许拓展 , 语法不一样

// interface extends interface
interface IName {
    name: string
}
interface IPerson extends IName {
    age: number
}
const p: IPerson = {
    name: 'ifer',
    age: 18,
}

// interface extends type
type TName = { name: string }
interface IPerson extends TName {
    age: number
}
const p: IPerson = {
    name: 'ifer',
    age: 18,
}

// type & type
type TName = { name: string }
type TPerson = { age: number } & TName
const p: TPerson = {
    name: 'ifer',
    age: 18,
}

// type & interface
interface IName {
    name: string
}
type TPerson = { age: number } & IName
const p: TPerson = {
    name: 'ifer',
    age: 18,
}

不同点

type 除了可以描述对象或函数,实际上可以为任意类型指定别名。

type NumStr = number | string

相同的 interface 声明能够合并,相同的 type 声明会报错。

interface IPerson {
    name: string
}
interface IPerson {
    age: number
}
const p: IPerson = {
    name: 'ifer',
    age: 18,
}

总结:一般使用 interface 来描述对象结构,用 type 来描述类型关系。

10元祖类型

使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字。

元组 Tuple,元组是特殊的数组类型,它能确定元素的个数以及特定索引对应的类型。

const position: [number, number] = [39.5427, 116.2317]

可以给元组中每一个元素起名字,见名知意。

// const arrTuple: [height: number, age: number, salary: number] = [170, 20, 17500]
function useState(num: number): [num: number, fn: (n: number) => void] {
    const setNum = (num: number): void => {}
    return [num, setNum]
}
const [num, setNum] = useState(10)
setNum(29)

11.类型推断

常见的发生类型推论的 2 种场景:声明变量并初始化时;决定函数返回值时。

技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型。

12.字面量类型

let str1 = 'Hello TS'
const str2 = 'Hello TS'

通过 TS 类型推论机制,可以得到答案:变量 str1 的类型为:string,变量 str2 的类型为:'Hello TS'。

str1 是一个变量,它的值可以是任意字符串,所以类型为:string。

str2 是一个常量,它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'(字符串字面量类型)

1.使用场景

使用方式:字面量类型常配合联合类型一起使用

type Direction = 'up' | 'down' | 'left' | 'right'
function changeDirection(direction: Direction) {
    console.log(direction)
}
changeDirection('up') // 调用函数时,会有类型提示
type Gender = '男' | '女'
const zs: Gender = '男'
type Action = {
    type: 'TODO_ADD' | 'TODO_DEL' | 'TODO_CHANGE' | 'TODO_FIND'
}

function reducer(state, action: Action) {
    switch (action.type) {
        case 'TODO_ADD': // 这里会自动具有提示
    }
}

13.枚举类型

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。

使用 enum 关键字定义枚举,约定枚举名称以大写字母开头。

// 创建枚举
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

// 可以当做类型使用枚举
function changeDirection(direction: Direction) {
    console.log(direction)
}

// 也可以当做值使用枚举
// 调用函数时,需要传入:枚举 Direction 成员的任意一个,类似于 JS 中的对象,直接通过点(.)语法 访问枚举的成员
changeDirection(Direction.Up)

1.数字枚举

// Down -> 11Left -> 12Right -> 13
enum Direction {
    Up = 10,
    Down,
    Left,
    Right,
}

enum Direction {
    Up = 2,
    Down = 4,
    Left = 8,
    Right = 16,
}
console.log(Direction['Up']) // 2
// 也可以反向操作
console.log(Direction[2]) // Up

2.字符串枚举

字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

enum Gender {
    ,
    ,
}
type User = {
    name: string
    age: number
    // gender: '男' | '女' // 但后台需要 0  1
    gender: Gender
}

const user: User = {
    name: 'ifer',
    age: 18,
    gender: Gender.男,
}

14.类型断言

使用 as 关键字实现类型断言。

技巧:打开浏览器控制台,选中标签,通过 $0.proto 可以获取 DOM 元素的类型。

// 注意 document.querySelector('a') 这种写法会自动推断出是 HTMLLinkElement 类型
const oLink = document.getElementById('link')
该方法返回的类型是 HTMLElement,该类型只包含所有标签公共的属性或方法,
不包含 a 标签特有的 href 等属性,这个类型太宽泛(不具体),
无法操作 href 等 a 标签特有的属性或方法
const oLink = document.getElementById('link') as HTMLAnchorElement
const oLink = <HTMLAnchorElement>document.getElementById('link')

15.typeof

注意 typeof 出现在类型注解的位置(参数名称的冒号后面,区别于 JS 代码)。

JS 中的 typeof 可以在运行时判断类型,TS 中的 typeof 可以在编译时获取类型。

interface Person {
    name: string
    age: number
}
const person: Person = { name: 'ifer', age: 18 }

// 获取 person 的类型,得到的就是 Person 接口类型
type p = typeof person

TS 中 typeof 的使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。

const p = { x: 1, y: 2 }
function formatPoint(point) {} // 没有提示
function formatPoint(point: { x: number; y: number }) {} // 有提示,写法麻烦
// 使用 `typeof` 操作符来获取变量 p 的类型,结果与上面对象字面量的形式相同
function formatPoint(point: typeof p) {} // 推荐

16.keyof

作用:获取接口、对象(配合 typeof)、类等的所有属性名组成的联合类型。

// 接口
interface Person {
    name: string
    age: number
}
type K1 = keyof Person // "name" | "age"
type K2 = keyof Person[] // "length" | "toString" | "pop" | "push" | "concat" | "join"
// 对象(要配合 typeof 才能使用)
const obj = { name: 'ifer', age: 18 }
/* type newobj = typeof obj
type keyofObj = keyof newobj // "name" | "age" */

// 简写
type keyofObj = keyof typeof obj // "name" | "age"
let s1: keyofObj = 'name' // ok
let s2: keyofObj = 'xxx' // error

17.特殊类型

1.any

当值的类型为 any 时,可以对该值进行任意操作

let num: any = 8 // 任意类型,不对类型进行校验
num.toFixed() // 没有提示
num = 'xxx' // 可以赋任意值(即可以把任意值给 any 类型)

2.unknow

unknown: 任意类型,更安全的 any 类型。

let num: unknown = 88
num = 'abc'
console.log(num)
num() // error: 不能调用方法
console.log(num.length) // error: 不能访问属性

可以使用类型收窄来处理 unknown 类型

let num: unknown = 88
if (typeof num === 'string') {
    console.log(num.length)
} else if (typeof num === 'function') {
    num()
}

并不是所有的类型都可以进行收窄

let num = 'hello' // num 的类型已经确定就是 string 类型
if (typeof num === 'string') {
    console.log(num.length)
} else if (typeof num === 'function') {
    // 如果再等于了 function 类型,那是不可能的,所以 num 被推断为了 never 类型
    num() // Error
}

unknown 类型可以配合断言使用。

let num: unknown = 88
let len = (num as string).length
console.log(len)

比较 :

任何类型可以给 any,any 也可以给任何类型。

任何类型可以给 unknown,unknown 只能给 unknown 或 any 类型

3.never

不可能实现的类型

type Test = number & string

// 也可以当做函数的返回值,表示不会执行到头
function test(): never {
    throw new Error('Error')
}

4.null 和 undefined

let str: string = 'ifer'

// 默认情况下,tsconfig.json 中的 strictNullChecks 的值为 false
// undefined 和 null 是其他类型的子类型,也就是可以作为其他类型的值存在

str = undefined
str = null

18.函数重载

需求:改造下面的函数,输入 ['a', 'b', 'c'],输出 ['Hello a', 'Hello b', 'Hello c']。

function greet(name: string): string {
    return `Hello ${name}`
}

方法 1,使用联合类型实现

function greet(name: string | string[]): string | string[] {
    if (typeof name === 'string') {
        return `Hello ${name}`
    } else if (Array.isArray(name)) {
        return name.map((name) => `Hello ${name}`)
    }
    throw new Error('异常')
}
const r = greet(['a', 'b', 'c'])
console.log(r)

方法 2,使用函数重载实现

// 一个函数可以有多个重载签名
// !重载签名:包含了函数的参数类型和返回值类型,但不包含函数体
function greet(name: string): string
function greet(name: string[]): string[]

// 一个函数只能有一个实现签名
// !实现签名:参数和返回值要覆盖上面的情况(更通用),且包含了函数体
function greet(person: unknown): unknown {
    if (typeof name === 'string') {
        return `Hello ${name}`
    } else if (Array.isArray(name)) {
        return name.map((name) => `Hello ${name}`)
    }
    throw new Error('异常')
}

console.log(greet(['a', 'b', 'c']))

19.泛型

泛型:定义时宽泛、不确定的类型,需要使用者去主动传入。

1.泛型函数

语法:在函数名称的后面添加 <>(尖括号),尖括号中添加类型变量

类型变量 Type,可以是任意合法的变量名称,一般简写为 T

function id<Type>(value: Type): Type {
    return value
}
function id<T>(value: T): T {
    return value
}

const num = id<number>(10)
const str = id<string>('a')

可以用类型推断来简化代码

let num = id(10) // 省略 <number> 调用函数
let str = id('a') // 省略 <string> 调用函数

2.泛型约束

// 其实泛型 Type 约束的是数组里面的元素
function id<Type>(value: Type[]): Type[] {
    console.log(value.length)
    return value
}

id<string>(['a', 'b'])

添加泛型约束

interface ILength {
    length: number
}

// Type extends ILength 添加泛型约束
// 表示传入的类型必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends ILength>(value: Type): Type {
    console.log(value.length)
    return value
}

id('abc')
id(['a', 'b', 'c'])
id({ length: 8 })

// T 也可以继承字面量类型
function id<T extends { length: number }>(value: T): number {
    return value.length
}

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如第二个类型变量受第一个类型变量约束)。

function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')

3.泛型接口

接口也可以配合泛型来使用,以增加其灵活性,增强其复用性

interface User<T> {
    name: T
    age: number
}
const user: User<string> = {
    name: 'ifer',
    age: 18,
}
interface IdFunc<Type> {
    id: (value: Type) => Type // 接收什么类型,返回什么类型
    ids: () => Type[] // 返回值是,根据接收到的类型组成的数组
}
let obj: IdFunc<number> = {
    id(value) {
        return value
    },
    ids() {
        return [1, 3, 5]
    },
}

4.泛型工具类型

1.Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选

type Props = {
    id: string
    children: number[]
}

// 构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的啦
type PartialProps = Partial<Props>

2.Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)

type Props = {
    id: string
    children: number[]
}
// 构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的啦
type ReadonlyProps = Readonly<Props>

let props: ReadonlyProps = { id: '1', children: [] }
props.id = '2' // Cannot assign to 'id' because it is a read-only property

Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型

Pick 工具类型有两个类型变量,1. 表示选择谁的属性,2. 表示选择哪几个属性

interface Props {
    id: string
    title: string
    children: number[]
}
// 摘出 id 和 title
type PickProps = Pick<Props, 'id' | 'title'>

Omit,和 Pick 相反,表示排除的意思。

// 排除 id 和 title
type OmitProps = Omit<Props, 'id' | 'title'>

20.类型声明文件

  • TS 中有两种文件类型,分别是 .ts 和 .d.ts 文件。
  • .ts 文件:既可以包含类型信息又可以包含可执行代码。
  • a,可以被编译为 .js 文件,然后执行代码。
  • b,用途:编写程序代码的地方。
  • .d.ts 文件:只包含类型信息的类型声明文件。
  • a,不会生成 .js 文件,仅用于提供类型信息,在 .d.ts 文件中不允许出现可执行的代码,只用于提供类型。
  • b,用途:为 JS 提供类型信息。
  • 总结:.ts 是 implementation(代码实现文件),.d.ts是 declaration(类型声明文件),如果要为 JS 库提供类型信息,需要使用.d.ts 文件。