typescript知识

为什么使用 ts

  • 提供类型检查,减少错误
  • 更友好的代码提示
  • 增强代码可读性和可维护性
  • 更好的代码重构
  • 更好的代码组织和管理
  • JS 的超集,扩展新功能

转换

.ts 文件并不被浏览器所识别,运行不了。

所以需要转换为 .js 文件才能被浏览器所识别并运行。

我们可以安装 typescript,使用它提供的 tsc 来编译 .ts 文件。

安装:npm i -g typescript

转换:tsc xx.ts

不过,重新修改文件后需要重新运行转换命令。

如果想修改后自动进行装换,那么需要使用下面的命令。

自动监听的转换命令:tsc xx.ts -w

.ts 文件设置全局或局部环境

.ts 文件默认是全局环境的。也就是说,我们现在有两个文件,一个 a.ts,一个 b.ts。

a.ts 中定义了一个变量 a,let a = 1,在 b.ts 中输出变量 a,console.log(a)

上面的代码不是不有警告的,b.ts 能够知道全局环境中有定义的 a 变量。

这样就不好了,我们希望 b.ts 只知道自己定义的 a 变量,而不应该知道全局环境中定义的 a 变量。

解决办法:只要文件使用模块化操作,那么该文件就会变为局部环境。

方法:在每个文件末尾添加 export {},这样 a.ts、b.ts 就变成了一个个独立的模块,b.ts 就只能使用自己定义的 a 变量了。

生成 tsconfig.json 文件

命令:tsc --init

联合类型

多个类型,只有符合其中的一个条件即可。

类型之间进行或操作。

let a: string | number = '1'
a = 1

type A = { name: string }
type B = { age: number }

let person1: A | B = { name: 'zhangsan' }

交叉类型

多个类型,必须符合所有条件。

类型之间进行与操作。

type A = { name: string }
type B = { age: number }

let person1: A & B = { name: 'zhangsan', age: 18 }

never

表示永远不会出现的值的类型。

很少用,一般是程序自动推断出来的。

let a: string & number = 1 // 不能将类型“1”分配给类型“never”。

也可以手动定义。

function foo(n: 1 | 2 | 3) {
switch (n) {
case 1:
break
case 2:
break
case 3:
break
default: // 这里是永远无法走到的
let m: never = n
break
}
}

any

表示任意类型,任何类型都可以赋值给 any 类型。

any 使用时,TS 不进行检测。

unknown

表示 any 类型对应的安全类型。

unknow 使用时,TS 会进行检测。

类型断言

当 TS 推断出来的类型并不满足我们的使用需要时,可以使用类型断言来手动指定一个类型。

let a: unknown = [1, 2, 3, 4];
(a as []).map(() => { })

非空断言

数组

定义方法:

  • 类型[]
  • Array<类型> – 泛型的写法
let arr1: number[] = [1, 2, 3, 4]
let arr2: string[] = ['1', '2', '3', '4']
let arr3: Array<number> = [1, 2, 3, 4]
let arr4: Array<string> = ['1', '2', '3', '4']
let arr5: (string | number)[] = ['1', '2', 3, 4]

元祖

表示一个已知元素数量和类型的数组,各元素的类型不必相同。

注意定义的元素类型和元素的数量和它的类型必须一致。

let arr1: [string, number] = ['1', 1]
let arr2: [string, number, string] = ['1', 2, '3']

定义对象、可选属性、索引签名

type Person = {
name: string
age: number,
sex?: string // 可选属性
[index: string]: any // 可以是任意属性,索引签名
}

const person: Person = {
name: 'John',
age: 30,
address: 'New York' // 可以是任意属性,这里就是多的,符合索引签名的定义
}

需要注意的是,代码的提示只会提示定义的属性,不会提示索引签名的属性。

如上面会提示的属性有 name、age、address。

空数组、空对象

const arr: number[] = [] // 空数组

type ObjType = {
name: string,
age: number
}
const obj = {} as ObjType // 空对象

定义函数、void类型

函数的实参个数和形参个数必须一致。

function foo1 (a: string, b?:number): number {
return 123
}
foo1('1', 2)

const foo2 = (a: string, b?: number): number => {
return 123
}
foo2('1', 2)

const foo3: (a: string, b?: number) => number = (a, b) => {
return 123
}
foo3('1', 2)

type FooType = (a: string, b?: number) => number
const foo4: FooType = (a, b) => {
return 123
}
foo4('1', 2)

void 类型是函数没有返回值的类型。

// 返回 void 可以不写return
// 也可以写 return,但后面不跟内容,或者跟 undefined
function foo1 () { }

function foo2 (): void {
return
}

function foo3 (): void {
return undefined
}

如果返回的是 undefined 需要手动指定返回类型为 undefined。

函数重载

现在需要实现一个函数,它接受一个、两个、四个参数,不能只传三个参数,因为第三个、第四个参数是搭配一起的。

// 函数重载
function foo(n1: string): any
function foo(n1: string, n2: number): any
function foo(n1: string, n2: number, n3: string ,n4: string): any
function foo(n1: string, n2?: number, n3?: string, n4?: string): any{}
foo('1')
foo('1', 2)
// foo('1', 2, '3') // Error
foo('1', 2, '3', '4')

可调用注解

type Foo = {
(n1: string): any
(n1: string, n2: number): any
(n1: string, n2: number, n3: string, n4: string): any
username?: string // 添加属性
}
function foo(n1: string, n2?: number, n3?: string, n4?: string): any{}

let fn: Foo = foo
fn('1')
fn('1', 2)
// fn('1', 2, '3') // Error
fn('1', 2, '3', '4')
fn.username = 'haha'
console.log(fn.username) // 'haha'

枚举

可以通过枚举的属性名获取枚举的值,也可以通过枚举的值获取枚举的属性名。

enum UserType {
SUPER_ADMIN,
ADMIN,
USER
}

console.log(UserType.SUPER_ADMIN) // 0
console.log(UserType.ADMIN) // 1
console.log(UserType.USER) // 2
console.log(UserType[0]) // SUPER_ADMIN
console.log(UserType[1]) // ADMIN
console.log(UserType[2]) // USER

可以手动定义枚举的值。后面的枚举值会自动递增。

enum UserType {
SUPER_ADMIN,
ADMIN = 3,
USER
}

console.log(UserType.SUPER_ADMIN) // 0
console.log(UserType.ADMIN) // 3
console.log(UserType.USER) // 4
console.log(UserType[0]) // SUPER_ADMIN
console.log(UserType[3]) // ADMIN
console.log(UserType[4]) // USER

也可以定义为字符串,但定义为字符串后,所有的枚举值都必须手动赋值。

enum UserType {
SUPER_ADMIN = 'super_admin',
ADMIN = 'admin',
USER = 'user'
}

console.log(UserType.SUPER_ADMIN) // super_admin
console.log(UserType.ADMIN) // admin
console.log(UserType.USER) // user
enum UserType {
SUPER_ADMIN = 'super_admin',
ADMIN = 2,
USER = 'user'
}

console.log(UserType.SUPER_ADMIN) // super_admin
console.log(UserType.ADMIN) // 2
console.log(UserType.USER) // user

const 枚举

未使用 const 定义的枚举,编译结果对比

//.ts 文件
enum UserType {
SUPER_ADMIN = 'super_admin',
ADMIN = 2,
USER = 'user'
}

console.log(UserType.SUPER_ADMIN) // super_admin
console.log(UserType.ADMIN) // 2
console.log(UserType.USER) // user

编译结果

//.ts 文件编译后的.js 文件代码
var UserType;
(function (UserType) {
UserType["SUPER_ADMIN"] = "super_admin";
UserType[UserType["ADMIN"] = 2] = "ADMIN";
UserType["USER"] = "user";
})(UserType || (UserType = {}));
console.log(UserType.SUPER_ADMIN); // super_admin
console.log(UserType.ADMIN); // 2
console.log(UserType.USER); // user

使用 const 定义的枚举,会在编译时被移除,不会生成对应的代码。

// .ts 文件
const enum UserType {
SUPER_ADMIN = 'super_admin',
ADMIN = 2,
USER = 'user'
}

console.log(UserType.SUPER_ADMIN) // super_admin
console.log(UserType.ADMIN) // 2
console.log(UserType.USER) // user

编译后

// .ts 文件编译后的 .js 文件代码
console.log("super_admin" /* UserType.SUPER_ADMIN */); // super_admin
console.log(2 /* UserType.ADMIN */); // 2
console.log("user" /* UserType.USER */); // user

接口 与 类型别名 的区别

接口是一系列抽象方法的声明,是一些方法特征的集合。

简单来说,接口的作用就是为这些类型命名和为我们的代码或第三方代码定义契约。

区别:

  • 接口主要用于对象类型,类型别名均可以。
  • 相同接口定义可以进行合并,类型别名不行。
  • 接口支持继承,类型别名不行。
  • 类型别名支持映射类型,接口不支持。
// 接口主要用于对象类型,类型别名均可以
type A = string
type B = number[]

interface C { }

// 相同接口定义可以进行合并,类型别名不行。
interface D {
a: string
}
interface D {
b: string
}
const e: D = {
a: 'a',
b: 'b'
}

// 接口支持继承,类型别名不行。
interface F extends D {
c: string
}

字面量类型

可以把字面量作为具体的类型使用,需要注意的是,该类型的取值就必须是该字面量的值。

type Type = 'admin' | 'test'
const userType: Type = 'admin'

keyof 关键字

interface IObj {
name: string
age: number
}
// keyof 可以获取对象的所有属性名的联合类型,即 'name' | 'age'
// keyof IObj 就是 'name' | 'age',只能选择这两个值
const user: keyof IObj = 'age'


const obj = {
name: 'lisi',
age: 18
}
// typeof obj 就是 {name: string, age: number}
// keyof typeof obj 就是 'name' | 'age'
const user2: keyof typeof obj = 'age'

类型保护

类型保护允许我们使用更小范围下的对象类型。

  • typeof
  • instanceof
  • in
  • 字面量类型
// typeof
function fn(n: number | string) {
if (typeof n === 'string') {
console.log(n.length)
}
}

// instanceof
class userName {
name!: string // 非空断言
}
class userAge {
age!: number
}
function fn1(n: userName | userAge) {
if (n instanceof userName) {
console.log(n.name)
}
}

// in
function fn2(n: {name: string} | {age: number}) {
if ('name' in n) {
console.log(n.name)
}
}

// 字面量类型
function fn3(n: 'a' | 123) {
if (n === 'a') {
console.log(n.length)
}
}

自定义类型保护

// is 是类型谓语
function isStr(n: any): n is string {
return typeof n === 'string'
}

function fn(n: number | string) {
if (isStr(n)) {
console.log(n.length)
}
}
fn('123')

泛型

指在定义函数、接口或类时,未指定参数类型,在运行时才确定。

// 类型别名
type A<T> = T
const a: A<string> = 'string'
const b: A<number> = 1

// 接口
interface IA<T> {
(n: T): T
name?: T
}
const c: IA<string> = (n: string): string => { return n}
c.name = 'string'

// 函数
function fn<T> (n: T): T {
return n
}
fn(1)

// 类
class AClass<T> {
name: T
constructor (name: T) {
this.name = name
}
getName (): T {
return this.name
}
setName (n: T): string {
this.name = n
return 'success'
}
}
const d = new AClass<string>('string')
console.log(d.getName())
console.log(d.setName('haha'))

泛型约束

class AClass<T> {
name: T
constructor (name: T) {
this.name = name
}
}
const d = new AClass<string>('123') // 任何类型
const e = new AClass<number>(123)
class BClass extends AClass<string> { } // Bclass 只能是 string 类型
const f = new BClass('string')


type B = string
function fn1<T extends B> (n: T): T { // 只能为 string 类型
return n
}
fn1('string')

类型兼容性

类型兼容性用于确定一个类型是否能赋值给其他类型。

// 基础类型的兼容性
let a: string = '123'
let b: string | number = 123
// a = b // error
b = a // ok

// 对象类型的兼容性
interface A {
a: number
}
interface B {
a: number
b: string
}
let c: A = { a: 1 }
let d: B = { a: 1, b: '1' }
c = d // ok
// d = c // error

// 函数类型的兼容性
function fn({ a: number }) {}
fn({ a: 1 })
// fn({ a: 1, b: 2 }) // error
const value = { a: 1, b: 2 }
fn(value) // ok

对于基础类型来说,少的可以赋值给多的。
对于对象类型来说,多的可以赋值给少的。
对于函数类型来说,实参多的可以赋值给形参少的。

映射类型

只能通过 类型别名 来定义映射类型。不能使用 接口 来定义映射类型。

type A = {
username: string
age: number
}
type B = {
[p in keyof A]: A[p]
}

const a: B = { username: 'ss', age: 12 }

keyof A 是获取类型 A 的所有属性名的联合类型,即 'username' | 'age'
p in keyof A 是遍历类型 A 的所有属性名,即 'username' | 'age'
A[p] 是获取 当前遍历属性 p 在 类型 A 中的类型,即 string | number

内置工具类型


type A = {
username: string
age: number
gender?: string,
readonly address?: string
}

type a = Readonly<A> // 全部变为只读属性
/*
type a = {
readonly username: string;
readonly age: number;
readonly gender?: string;
readonly address?: string;
}
*/

type b = Partial<A> // 全部变为可选属性
/*
type b = {
username?: string;
age?: number;
gender?: string;
readonly address?: string;
}
*/

type c = Pick<A, 'username' | 'age'> // 挑选属性
/*
type c = {
username: string;
age: number;
}
*/

type d = Record<'age', string> // 把所有属性变为指定类型
/* type d = { age: string; } */
type e = Record<keyof A, string>
/*
type e = {
username: string;
age: string;
gender: string;
address: string;
}
*/

type f = Required<A> // 全部变为必选属性
/*
type f = {
username: string;
age: number;
gender: string;
readonly address: string;
}
*/

type g = Exclude<string | number | boolean, boolean> // 排除指定类型
/* type g = string | number; */

type h = Extract<string | number | boolean, boolean> // 提取指定类型
/* type h = boolean; */



type i = Omit<A, 'username' | 'age'> // 剔除指定属性
/*
type i = {
gender?: string;
readonly address?: string;
}
*/

type j = NonNullable<string | number | undefined | null> // 排除null和undefined
/* type j = string | number; */

infer 关键字

type A<T> = T extends Array<infer U> ? U : T
// infer U 用来推断数组元素的类型
// 为什么加 infer,因为我们不知道数组的元素类型是什么,所以需要推断出来

type B = A<Array<number>>
type C = A<string>

类如何使用类型

类中定义类型

class A {
username!: string
}
class B {
username: string = 'zhangsan'
}
class C {
username: string
constructor (username: string) {
this.username = username
}
}
const a = new A()
a.username = 'zhangsan'
const b = new B()
b.username = 'zhangsan'
const c = new C('zhangsan')
console.log(a, b, c); // A { username: 'zhangsan' } B { username: 'zhangsan' } C { username: 'zhangsan' }
c.username = 'lisi'
console.log(a, b, c); // A { username: 'zhangsan' } B { username: 'zhangsan' } C { username: 'lisi' }

类使用接口

interface IA {
username: string
getUserInfo(age: number): string
}

class A implements IA {
username: string = '张三'
getUserInfo(age: number): string {
return `我叫${this.username},今年${age}岁了`
}
}
const a = new A()
console.log(a.getUserInfo(18))

类使用泛型

class A<T> {
username: T
constructor(username: T) {
this.username = username
}
}
const a = new A<string>('123')

class B extends A<number>{ }
const b = new B(123)


// 泛型接口
interface IA<T> {
username: T
getUserInfo(age: number): string
}

class A implements IA<string> {
username: string = '张三'
getUserInfo(age: number): string {
return `我叫${this.username},今年${age}岁了`
}
}
const a = new A()
console.log(a.getUserInfo(18))