ts知识记录

107 阅读14分钟

TS编译环境

tsc:typeScript compile r

安转

    npm install typeScript -g

查看版本

   tsc --Version

编译

  • 方法一
   tsc 文件名
  • 方法二
    npm i ts-node
  • 方法三:通过webpack搭建一个ts的环境

搭建环境

生成tsconfig.json

   tsc --init

变量的类型推导(推断)

image.png

image.png

codeWhy: 默认情况下,如果可以推导出对应的标识符的类型时,一般情况下是不加类型注解

数据类型

image.png

JavaScript类型

数组类型

// array
// arr是数组类型,但是数组中存放的是什么类型的元素?
// 一个数组中的TypeScript开发中,最好存放的数据类型是固定的
let arr = [] 
arr.push('123')
// 类型注解:type annotation
const arr1:string[] = []
// arr1.push(123)
const arr2:Array<string> = [] // 不推荐(jsx中是有冲突的) <div></div> ---- <string>
// arr2.push(123)

Object类型

const obj = {
    name: 'zhangsan',
    height: 170
}
console.log('obj', obj.name)

image.png

const obj:object = {
    name: 'zhangsan',
    height: 170
}
console.log('obj', obj.name)

image.png

Null Undefined

image.png

// 所以从一开始就确定N的类型
let N: null = null
let N: string = null
N = 'abc' // 如果开启了严格模式则会报错
let N:null = null;
let n2:undefined = undefined 

小知识:JS

  1. null:空值,曾赋过值,但是目前没有值
  2. undefined: 没有值,从未赋值

Symbol类型

const title1 = Symbol('title')
const title2 = Symbol('title')
const info = {
    [title1]: '程序员',
    [title2]: 'laoshi'
}
console.log('info', info[title1])

TypeScript类型

any类型

在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart语言中的dynamic类型)。

  • 当进行一些类型断言,因为一些类型断言不能直接进行转化,as any
  • 不想给某些JavaScript添加具体的数据类型时(相当于原生的JavaScript)

any的使用在类型检测的角度来看是不安全的

  • 如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any:
    • 包括在Vue源码中,也会使用到any来进行某些类型的适配

unknown类型

unknown是TypeScript中比较特殊的一种类型,它用于描述不确定的变量

unknown与any的区别

  • unknown类型只能赋值给anyunknown类型
  • any类型可以赋值给任意类型
function foo () {
    return 'abc'
}
function bar () {
    return 122
}

let flag = true
// unknown类型只能赋值给any和unknown类型
let result:unknown // 最好不要使用any
// let result:any
if (flag) {
    result = foo()
} else {
    result = bar()
}
let mes:string = result
console.log(result);

image.png

防止数据类型乱用,unknown类型只能赋值给any和unknown类型

void类型

void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型

image.png

function sum(num1: number, num2: number): void{
    return undefined // (返回值类型可写可不写)
}
sum(1, 2)

一般情况下,不写void

never

表示永远不会发生值的类型

  • 比如一个函数
    • 死循环或抛出异常
// never应用在什么场景
function handleMessage(message: number | string)
    switch (typeof message) {
        case 'string':
            console.log('foo');
            break;
        case 'number':
            console.log('bar');
            break;
        default:
            const check: never = message
    }
}
handleMessage('123')
// never应用在什么场景
// function handleMessage(message: number | string)
function handleMessage(message: number | string | boolean) {
    switch (typeof message) {
        case 'string':
            console.log('foo');
            break;
        case 'number':
            console.log('bar');
            break;
        default:
            const check: never = message
    }
}
handleMessage('123')
handleMessage(true)

image.png

上述表明,当类型增加,switch的分支判断也应增加,若仅增加类型,走default进行never类型赋值则出现报错

tuple类型

元组类型

const info: any[] = ['bcvCoder', 16, 1.68]
let x = info[3] // x:any
console.log(x.length)

image.png

const info: [string, number, number] = ['bcvCoder', 16, 1.68]
let name = info[0] // name:string
console.log('name', name)
let x = info[3] // x:undefined
console.log(x.length)

image.png

应用

function useState<T>(state: T): [T, (newValue:T) => void] {
    let currentState = state
    const changeState = (newState: T) => {
        currentState = newState
    }
    const tuple:[T, (newState: T) => void] = [currentState, changeState]
    return tuple
}
const [counter, setCounter] = useState(10)
setCounter(1000)

简写

function useState<T>(state: T): [T, (newValue:T) => void] {
    let currentState = state
    const changeState = (newState: T) => {
        currentState = newState
    }
    // const tuple:[T, (newState: T) => void] = [currentState, changeState]
    // return tuple
    return [currentState, changeState]
}
const [counter, setCounter] = useState(10)
setCounter(1000)

函数类型

// 原函数const foo = () => {}
const foo: () => void = () => {} // 增加类型 () => void
// 函数类型不是写function,function只是一个关键字

更优雅的写法

const MyFunction = () => void
const foo: MyFunction = () => {}

函数

/**
 * 返回值加类型注释: ():number
 * 在开发中,通常情况下可以不写返回值的类型(不写会自动推导)
 */
function sum(num1: number, num2: number): number {
    return num1 + num2
}
  • 和变量的类型注解一样,通常情况下不需要返回类型注解,因为TypeScript会根据return返回值推断函数的返回类型
    • 某些第三方库处理方便理解,会明确指定返回 类型,但是这个看个人喜好

匿名函数

匿名函数的参数可以不指定类型(根据上下文环境推断出来的)

// 通常情况下,在定义一个函数时,都会给参数加上类型注解的
function foo(message:string) {

}
const names = ['abc', 'cba', 'nba']
// item根据上下文的环境推导出来的,这个时候可以不添加类型注解
// 上下文中的函数,可以不添加类型注解
names.forEach((item) => {
    
})

对象类型

/**
 * 可以是逗号也可以是分号
 * {x: number, y: number} {x: number; y: number}
 * 
 */
function printCoordinate(point: {x: number, y: number}) {
    console.log('x:坐标', point.x);
    console.log('y:坐标', point.y);
}
printCoordinate({x: 10, y: 30})

可选类型

?

z?: number ----> number | undefined

function printCoordinate(point: {x: number, y: number, z?: number}) {
    console.log('x:坐标', point.x);
    console.log('y:坐标', point.y);
    if (point.z) {
        console.log('z:坐标', point.z);
    }
}
printCoordinate({x: 10, y: 30})
printCoordinate({x: 10, y: 30, z: 40})

联合类型

|

function printId (id: number | string) {
    console.log('你的ID是:', id); 
}
printId(10)
printId('abc')

类型别名

type

type IDType = string | number | boolean
function printId(id: IDType) {}

类型断言

有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions) as 通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型

// <img id='imgDOM' />
const el = document.getElementById('imgDOM')
el.src = 'url地址'

image.png

image.png

// <img id='imgDOM' />
const el = document.getElementById('imgDOM') as HTMLImageElement
el.src = 'url地址'
class Person {}
class Student extends Person {
    saying() {}
}
function sayHello(p: Person) {
    p.saying()
}
const stu = new Student()
sayHello(stu)

image.png

class Person {}
class Student extends Person {
    saying() {}
}
function sayHello(p: Person) {
    (p as Student).saying()
}
const stu = new Student()
sayHello(stu)
const message = 'nihao'
const num:number = message

image.png

了解

TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换 更具体:具体类型;不太具体:any、unknown

const message = 'nihao'
// const num:number = (message as any) as number
const num:number = (message as unknown) as number

非特别情况,不建议使用,可能会造成类型混乱

非空类型断言

!.

/**
 * 
 * message? -> undefined | string
 */
function printMessage(message?: string) {
    console.log('message', message.length);
}
printMessage()

image.png

确定传入的参数是有值的,可以使用非空类型断言.非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

/**
 * 
 * message? -> undefined | string
 */
function printMessage(message?: string) {
    console.log('message', message!.length);
}
printMessage()

可选链

?.

当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行

可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更版本

type Person = {
    name: string
    friend?: {
        name: string
        age?: number
        girlfriend?: {
            name: string
        }
    }
}
const info:Person = {
    name: 'BrokerCV',
    friend: {
        name: 'cv'
    }
}

console.log(info.friend.age);

image.png

type Person = {
    name: string
    friend?: {
        name: string
        age?: number
        girlfriend?: {
            name: string
        }
    }
}
const info:Person = {
    name: 'BrokerCV',
    friend: {
        name: 'cv'
    }
}

console.log(info.friend?.age);
console.log(info.friend?.girlfriend?.name);

ES11(?.)

??和!!的作用

??

  • 它是ES11增加的新特性;
  • 空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数
const messageCon:string | null = null
const content = messageCon ?? 'cvCoder'
console.log(content);

!!

  • 将一个其他类型转换成boolean类型;
  • 类似于Boolean(变量)的方式
const messageCon = 'cvCoder'
const flag = !!messageCon
console.log(flag);

字面量类型

image.png

字面量类型和值保持一致

// const str: string = 'cvCoder'
const str: string = 'cvCoder'
const strType:'cv' = 'cv'
let numType:123 = 123
numType = 23

image.png

字面量类型的意义,就是必须结合联合类型

type alignData = 'left' | 'right' | 'center'
function changeAlign(align: alignData) {
    console.log('修改方向', align)
}
changeAlign('left')

字面量推理

type Method = 'GET' | 'POST'
function request(url: string, method: Method) {

}
const options = {
    url: 'http://www.baidu.com',
    method: 'POST'
}
request(options.url, options.method)

image.png

// ptions.method存在安全隐患
options.method = '123'

解决

  • 方法一

推荐方法

type Method = 'GET' | 'POST'
function request(url: string, method: Method) {

}
type RequestType = {
    url: string,
    method: 'GET' | 'POST'
}
const options:RequestType = {
    url: 'http://www.baidu.com',
    method: 'POST'
}
request(options.url, options.method)
  • 方法二
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {

}
const options = {
    url: 'http://www.baidu.com',
    method: 'POST'
}
request(options.url, options.method as Method)
  • 方法三

字面量推理:as const

type Method = 'GET' | 'POST'
function request(url: string, method: Method) {

}
const options = {
    url: 'http://www.baidu.com',
    method: 'POST'
} as const
request(options.url, options.method)

类型缩小

什么是类型缩小呢?

  • 类型缩小的英文是 Type Narrowing;
  • 我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径;
  • 在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;
  • 而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards);

常见的类型保护有如下几种:

  • typeof
  • 平等缩小(比如===、!==)
  • instanceof
  • in
  • 等等...

函数类型

// void可以改为更明确的类型,如下都是返回的都是数值类型,void可以改为number
// num1、 num2两个参数名不可以省略,如果省略,类型则为参数名,真正的类型为any
type CalcFunc = (num1: number, num2: number) => void

function calc(fn: CalcFunc) {
    console.log(fn(20, 30));
}
function sum(num1: number, num2: number) {
    return num1 + num2
}
function mul(num1: number, num2: number) {
    return num1 * num2
}
calc(sum)
calc(mul)

参数的可选类型

可选类型必须写在必选类型的后面

// y -> undefined | number
function selectedOptions(x:number, y?:number) {
    console.log(x, y);
}
selectedOptions(10, 20)
selectedOptions(10)

image.png

function

默认值

function foo(x: number = 20, y: number = 100) {
    console.log(x, y);
}
foo(10, 30) // 10 30
foo(undefined, 12) // 20 12

顺序:必传参数 -> 有默认值的参数 -> 可选参数

this

明确的指定this的类型

type ThisType = {name: string};
function eating(this: ThisType) {
    console.log(this.name, 'eating---');
}
const infoFn = {
    name: 'cvCoder',
    eating: eating
}
infoFn.eating()
eating.call({name: 'kobe'})
eating.apply({name: 'cvCoder'})
export{}

函数重载

类的定义

class Person {
    name: string
    age: number

    eating() {
        console.log(this.name + ' eating');
    }
}
const p = new Person()
export {}

image.png 初始化

  • 方法一
class Person {
    name: string = ''
    age: number = 0

    eating() {
        console.log(this.name + ' eating');
    }
}
const p = new Person()
export {}
  • 方法二
class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    eating() {
        console.log(this.name + ' eating');
    }
}
const p = new Person('cvCoder',  18)
console.log(p.name);
console.log(p.age);
p.eating()

类的继承

使用extends关键字来实现继承,子类中使用super来访问父类

class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    eating() {
        console.log(this.name + ' eating');
    }
}
class Student extends Person {
    sno: number
    constructor(name: string, age: number, sno: number) {
        super(name, age) // 调用父类的构造器constructor(传递name, age)
        this.sno = sno
    }
    // overwrite
    eating() {
        super.eating() // 调用父类的方法
        console.log('student eating');  
    }
    studying() {
        console.log('stu');
    }
}
const stu = new Student('cvCoder', 18, 12345678)
console.log(stu.name);
console.log(stu.age);
console.log(stu.sno);
stu.eating()
// const person = new Person('per', 12)
// console.log('person', person.name);
// console.log('person', person.age);
// person.eating()
export {}

类的多态

父类引用指向子类对象

class Animal {
    action() {
        console.log('animal action');
    }
}
class Dog extends Animal {
    action() {
        console.log('animal running');
    }
}
class Fish extends Animal {
    action() {
        console.log('animal swimming');
    }
}
// animal: dog / fish
// 多态的目的是为了写出更加具备通用性的代码
function makeActions(animals: Animal[]) {
    animals.forEach(animal => {
        // console.log('animal', animal);
        animal.action()
    })
}
makeActions([new Dog(), new Fish])

类的成员修饰符

  • public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;

  • private 修饰的是仅在同一类中可见、私有的属性或方法;

  • protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;

class Person {
    private name: string = 'cv'
    // 封装了两个方法,通过方法来访问name
    getName() {
        return this.name
    }
    setName(newName: string) {
        this.name = newName
    }
}
const p = new Person()
// console.log(p.name);
console.log('123', p.getName());
p.setName('cvCoder')
console.log(p.getName());
export { }
class Person {
    protected name: string = ''
}
class Student extends Person {
    getName() {
        return this.name
    }
}
const stu = new Student()
console.log(stu.getName());
// console.log(stu.name); // 无法访问

类的只读属性readonly

class Person {
    // readonly name: string = '123'
    readonly name: string
    age?: number
    readonly friend?: Person
    // 1.只读属性可以在爱构造器中赋值,赋值之后就不可以修改
    // 2.属性本身不能进行修改,但是如果它是对象类型,对象中的属性是可以修改的
    constructor(name: string, friend?: Person) {
        this.name = name
        this.friend = friend
    }
}
const p = new Person('cvCoder', new Person('kobe'))
console.log(p.name);
console.log(p.friend);
if (p.friend) {
    p.friend.age = 30
}
console.log(p.friend);
// p.name = 'ddd' // 无法为“name”赋值,因为它是只读属性

访问器 getter/setter

class Person {
    // 私有属性下划线开头(习惯、规范)
    private _name: string
    constructor(name: string) {
        this._name = name
    }
    // 访问器 setter/getter
    // setter
    set name(newName: string) {
        this._name = newName
    }
    get name() {
        return this._name
    }
}
const p = new Person('cvCoder')
p.name = 'cv'
console.log(p.name);

类的静态成员

在TypeScript中通过关键字static来定义:

class Person {
    // 实例属性
    name: string = ''
    age: number = 12
}
const p = new Person()
p.name = '124'

class Student {
    static time: string = '20:00'
    static attendClass() {
        console.log('去学习');
    }
}
console.log(Student.time); // 静态成员,直接可以通过类来访问
Student.attendClass()

抽象类

  • 继承是多态使用的前提

    • 所以在定义很多通用的调用接口时,通常会让调用者传入父类,通过多态来实现更加灵活的调用方式
    • 但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,可以定义为抽象方法
  • 什么是 抽象方法?在TS中没有具体实现的方法(没有方法体),就是抽象方法

    • 抽象方法,必须存在于抽象类中
    • 抽象类是使用abstract声明的类
  • 抽象类有如下特点

    • 抽象类是不能被实例的(也就是不能通过new创建)
    • 抽象方法必须被子类实现,否则该类必须是一个抽象类
function makeArea(shape: Shape) {
    return shape.getArea()
}
abstract class Shape {
    /**
     * 1.抽象函数可以没有实现体
     * 2.抽象类是不能被实例的(也就是不能通过new创建)
     * */ 
    abstract getArea():number
}
class Rectangle extends Shape {
    private width: number
    private height: number
    constructor(width: number, height: number) {
        super()
        this.width = width
        this.height = height
    }
    getArea() {
        return this.width * this.height
    }
}
class Cirle extends Shape {
    private r: number
    constructor(r: number) {
        super()
        this.r = r
    }
    getArea() {
        return this.r * this.r * 3.14
    }
}
const circle = new Cirle(12)
const rect = new Rectangle(12, 1)
console.log(makeArea(circle));
console.log(makeArea(rect))

类的类型

class Person {
    name: string = '123'
    eating() {}
}
const p = new Person()
const p1:Person = {
    name: 'cvCoder',
    eating() {}
}
function printPerson(p: Person) {
    console.log(p.name) ;
}
printPerson(new Person())
printPerson({name: 'cv', eating(){}})

接口

声明

// 1.通过类型(type)别名来声明对象类型
type InfoType = {
    name: string,
    age: number
}
// 
// 
/**
 * 2.接口interface声明对象类型
 *   规范:在接口名称前加 I
 *   可以定义可选类型
 *   可以定义只读属性
 */
interface IInfoType1 {
    readonly name: string
    age: number
    friend?: {
        name: string
    }
}

const info: IInfoType1 = {
    name: "cvCoder",
    age: 18,
    friend: {
        name: 'LSL'
    }
}

索引类型

// 通过interface来定义索引类型
interface IndexLanguage {
    [index: number]: string
}
const frontLanguage: IndexLanguage = {
    0: 'HTML',
    1: 'CSS',
    2: 'JS',
    3: 'TS'
}

函数类型

// type Calc = (n1: number, n2: number) => number
// 可调用接口
interface CalcFn {
    (n1: number, n2: number): number
}
function calc(num1: number, num2: number, calcFn: CalcFn) {
    return calcFn(num1, num2)
}

const add: CalcFn = (num1, num2) => {
    return num1 + num2
}
calc(20, 30 ,add)

阅读性推荐:类型别名(type)

接口的继承

interface ISwim {
    swimming: () => void
}
interface IFly{
    flying: () => void
}
interface IAction extends ISwim, IFly {
    
}
const action: IAction = {
    swimming() {},
    flying() {}
}

交叉类型

多个类型结合在一起

// 组合类型:组合类型
type ConnectType = number | string
type Direction = 'left' | 'right' | 'center'
// 组合类型:交叉类型
type WType = number & string // never
interface ISwim {
    swimming: () => void
}
interface IFly{
    flying: () => void
}
type MyType = ISwim | IFly
type MyType1 = ISwim & IFly
const obj:MyType = {
    flying() {}
}
const obj1:MyType1 = {
    flying() {},
    swimming() {}
}

image.png

接口的实现

interface ISwim {
    swimming: () => void
}
interface IEat {
    eating: () => void
}
class Animal {

}
// 继承:只能实现 单继承
// 实现:实现接口,类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
    swimming() {
        console.log('Fish is swimming');
    }
    eating() {
        console.log('Fish is eating')
    }
}

class Person implements ISwim {
    swimming() {

    }
}

// 固定了只能传Fish类
// function swimAction(swimable: Fish) {
//     swimable.swimming()
// }
// swimAction(new Fish())
// swimAction({swimming: function() {}})
// 编写一些公共的API:面向接口编程
// 所有 实现 了接口的类对应的对象,都是可以传入的
function swimAction(swimable: ISwim) {
    swimable.swimming()
}
swimAction(new Fish())
swimAction({swimming: function() {}})
swimAction(new Person())

interface和type的区别

  • interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?

    • 如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function;
  • 如果是定义对象类型,那么他们是有区别的:

    • interface 可以重复的对某个接口来定义属性和方法;(合并)

    • 而type定义的是别名,别名是不能重复的

interface IPerson {
    age: number
}
interface IPerson {
    name: string
}
const person: IPerson = {
    age: 18,
    name: 'cvCoder'
}

泛型

泛型接口的使用

interface IInfo<T1, T2>{
    name: T1,
    age: T2
}
const student: IInfo = {
    name: 'cvCoder',
    age: 18
}

image.png

这里没有推理类型

  • 方法一 加上类型
interface IInfo<T1, T2>{
    name: T1,
    age: T2
}
const student: IInfo<string, number> = {
    name: 'cvCoder',
    age: 18
}
  • 方法二 默认类型
interface IInfo<T1 = string, T2 = number>{
    name: T1,
    age: T2
}
const student: IInfo = {
    name: 'cvCoder',
    age: 18
}

泛型类的使用

class Point<T> {
    x: T
    y: T
    z: T
    constructor(x: T, y: T, z: T) {
        this.x = x
        this.y = y
        this.z = z
    }
}
const p1 = new Point(12, 23, 24) // 自动推导类型
const p2 = new Point<number>(12, 23, 24)
const p3: Point<string> = new Point('123', '23', '34')
/**
 * Point<string>
 * 不推荐,因为react jsx 中会写 <>,可能会有冲突
 * 解决方法一:类型推导
 * 解决方法二:在JSX以外的文件中进行类型确定
 * 
 */

const name1: string[] = ['a', 'b', 'c']
const name2: Array<string> = ['aa', 'bb', 'cc'] // 不推荐(因为react jsx 中会写 <>)

泛型的约束

引子

image.png

泛型T无法确定传入的数据类型一定有length这个属性

使用extends对其进行限制

interface ILength {
    length: number
}
function getLength<T extends ILength>(arg: T) {
    return arg.length
}
getLength(123)
getLength(true) // 报错

image.png

命名空间namespace

export namespace time {
    export function format() {
        return '2022-09-09'
    }
}
export namespace tool {
    export function format() {
        return 'cvCoder'
    }
}
import {time, tool} from './07_其他知识点/tool'
console.log('=======', time.format());
console.log(tool.format());

promise

image.png

promise的类型会决定then的类型

image.png

image.png

import axios, { AxiosResponse } from 'axios'

// axios的实例对象
axios.get('http://baidu.com').then((res: AxiosResponse) => {
  console.log(res)
})

学习笔记记录