ts

81 阅读12分钟

js缺陷:对于标识符没有任何的类型缺陷,有安全隐患

ts始于js,归于js,拥有js最新的特性

类型校验适合开发大型项目

Angular、Vue3、VScode、and-design使用了ts重写

ts使用webpack搭建,或者使用ts-node插件搭建

npm install ts-node -g

npm install tslib @types/node -g

通过ts-node ...来运行ts代码

类型推导

  • let 类型推导:通用类型
  • const 类型推导:字面量类型

let n: null = null
let u: undefined = undefined

let hello: string = 'hello qq' // string类型
let demo1 = 'message'  // string类型
const demo2 = 1.99  // 字面量类型

Array类型

let names: string[] = ['a', 'b', 'c'] // 数组类型,数组中存放的是字符串类型
let nun: Array<number> = [12,24] // 泛型,数组中存放的数字类型

Object类型

type infoType = {
    name: string
    age: number
}

let info: infoType = {
    name: 'wqq',
    age: 18
}

// 对象类型和函数类型的结合使用
type pointType = { 
  x: number
  y: number
  z?: number // 可选类型
}
function position(point: pointType) {}

Symbol类型

const title1: symbol = Symbol('title')
const title2: symbol = Symbol('title')

const person = {
    [title1]:'wqq',
    [title2]:'qqq'
}

函数的类型

// 明确指定参数的类型,返回值类型可以明确知道,也可以自动推导
function sum(n1: number, n2: number): number {
    return n1 + n2
}
const res = sum(12, 31)

// 作为参数的匿名函数 最好不要加类型注解,
const names: string[] = ['aa', 'bb']
names.forEach((item, index, arr) => {
})

any类型

无法确定一个变量的类型,并且他可能会发生改变,就可以使用 any 类型

不限制标识符的任意类型,可以进行任意操作(获取不存在的属性、方法)

let id :any = 'id'
console.log(id.length)

unknown类型

和any类型有点相似,但是unknown类型的值做任何事情都是不合法的

必须要进行类型缩小,才能进行对应的操作

let foo: unknown = 'aaa'

if (typeof foo === 'string') { // 类型缩小
    console.log(foo.length);
}

void类型

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

如果返回值的void类型,也可以返回undefined

// 没有返回值,或者返回undefined
function sum(n1: number, n2: number): void {
    console.log(n1 + n2);
    return undefined
}

// 应用场景:用来指定函数类型的返回值是void
type fooType = () => void
const foo:fooType = () => {}

// 举个例子
// 1.要求传入的函数的类型
type ExecFnType = (...args:any[]) => void
// 2.定义一个函数,传入的参数也是一个函数,且这个函数的类型是ExecFnType
function delayExecFn(fn:ExecFnType) {
    setTimeout(() => {
        fn('wqq',20)
    },1000)
}
// 3.执行上面的函数,传入一个匿名函数
delayExecFn((name,age) => {
    console.log(name, age);
})

// 基于上下文类型推导的函数中的返回值如果是void,不强制要求不能返回任何东西
const msg = ['aa','bb']
msg.forEach(() => {
    return 123
})

never类型(了解)

表示永远不会发生值的类型,很少去定义,会自动推导

用处:封装框架/封装一些类型工具的校验、类型体操的题目

tuple类型

元组数据结构中可以可以存放不同的数据类型,取出来的item也有明确的类型

// 1. 使用数组:数组中最好存在相同的类型数据,获取值之后不能明确知道对应的数据类型
const info1: any = ['wqq', 20, 1.66]

// 2. 使用对象,需要增加 key
const info2 = {
   name: 'wqq',
   age: 20,
   height: 1.66
}

// 3. 使用元组类型,在函数中使用最多(函数返回值)
const info3: [string, number, number] = ['wqq', 20, 1.66]

function useState(initialState: number): [number, (newValue: number) => void] {
   let stateValue = initialState
   function setValue(newValue: number) {
       stateValue = newValue
   }
   return [stateValue, setValue] // 返回元组
}
const [count, setCount] = useState(10)

联合类型 |

从两个或者多个其他类型组成的类型,表示可以是这些类型中的任何一个值,使用|符号

function printID(id:number|string) {
    // 需要类型缩小
    if(typeof id === 'string'){
        console.log(id.length);
    }else {
        console.log(id);
    }
}
printID(12)
printID('aaa')

interface和type的区别

类型别名和接口非常相似,在定义对象类型时,大部分时候可以任意选择使用

接口的几乎所有的特性都可以在type中使用

总结:声明对象类型使用 interface,其他的使用 type

1. type使用范围更广,接口类型只能用来声明对象

type NumberType = number
 type IDType = string | number

2. 声明对象时,interface可以多次声明,type不允许两个相同别名同时存在

interface PointType {
    x: number,
    y: number,
}
interface PointType {
    z?: number
}

3. interface支持继承

interface IPerson {
    name: string,
}

interface QQ extends IPerson {
    message: string
}

const Wqq:QQ = {
    name:'qqq',
    message:'hello'
}

4. interface可以被类实现

class Person implements IPerson {
    ...
}
type PointTYpe1 = { x: number, y: number }

interface PointType {
    x: number,
    y: number,
    z?: number
}

function PointCoordinate(point: PointType) {

}

交叉类型 &

表示需要满足多个类型,使用 &符号,通常对对象类型进行交叉

interface IPerson {
    name: string,
}

interface ICoder {
    message: string,
    coding:() => void
}

const info:IPerson & ICoder = {
    name:'qqq',
    message:'hello',
    coding:() => {}
}

类型断言 as

断言只能断言成具体的类型,或者 不太具体(any unknown)的类型

// 获取dom元素(一般用法)
const imgEl = document.querySelector('.img') as HTMLImageElement
imgEl.src = 'xxx'

const age:number = 19
// const age2 = age as string 错误做法

// ts类型检测来说是正确的,但代码本身不太正确
const age3 = age as any
const age4 = age3 as string

非空类型断言 !

非空类型断言(确保friend有值的情况下,才能使用)

interface IPerson {
    name: string,
    friend?:{ name:string }
}

const info: IPerson =  {
    name:'wqq'
}
// 访问一个可能为空的属性,可以使用可选链 ?.
console.log(info.friend?.name);

// 给属性赋值
if(info.friend) {info.friend.name = 'qq'} // 类型缩小

info.friend!.name = 'www' // 非空类型断言

字面量类型

// 将多个字面量类型联合起来
type Direction = 'right' | 'left' | 'top' | 'down'
const d1: Direction = 'down'

// 例子
type methodType = 'get' | 'post'
function request(url: string, method: methodType) {}
request('ww', 'get')

// ts细节
const info1 = {
    url: 'www',
    method: 'get'
}
request(info1.url,info1.method) // 错误的做法,info.method获取的类型是string

// 1. 进行类型断言
request(info1.url, info1.method as 'get')

// 2. 定义类型
const info2: { url: string, method: 'get' } = {
    url: 'www',
    method: 'get'
}
request(info2.url, info2.method)

// 3. 字面量推理
const info3 = {
    url: 'www',
    method: 'get'
} as const
request(info3.url, info3.method)

类型缩小

// 1. typeof(用的最多)
function foo(value: string | number) {
    if (typeof value === "string") {
        console.log(value.length);
    } else {
        console.log(value);
    }
}

// 2. === 方向类型判断
type Direction = 'left' | 'right' | 'top'

function SwitchDirection(direction: Direction) {
    if (direction === "left") {
        console.log('左边');
    } else if (direction === 'right') {
        console.log('右边');
    } else {
        console.log('上边');
    }
}

// 3. instanceof: 传入一个日期,打印日期
function printDate(date: string | Date) {
    if (typeof date === "string") {
        console.log(date);
    } else {
        date.getDate()
    }
    // 实现2:判断是不是某一个类的实例
    if (date instanceof Date) {
        date.getDate()
    } else {
        console.log(date);
    }
}

// 4. in 判断是否有某一个属性
interface ISwim { swim: () => void }

interface IRun { run: () => void }

const fish: ISwim ={ swim:() => {} }
const dog: IRun = { run:() => {} }

function move(animal: ISwim | IRun) {
    if('swim' in animal){
        animal.swim()
    }else if ('run' in animal){
        animal.run()
    }
}

函数

函数类型表达式

格式:(参数列表)=> 返回值,对于传入的多余参数会被忽略掉

type fooType = (n1: number) => number
const foo: fooType = (arg: number): number => {
    return 123
}

// 练习
type CalcType = (n1: number, n2: number) => number
function calc(calcFn: CalcType) {
    const n1 = 10
    const n2 = 20

    const res = calcFn(n1, n2)
}

function sum(n1: number, n2: number) {
    return n1 + n2
}
calc(sum)

// 匿名函数会自动进行类型推导
calc(function (n1, n2) {
    return n1 * n2
})

很多类型不报错,取决于它内部的规则

interface IPerson {
    name:string
    age:number
}

const p = {
    name:'ww',
    age: 21,
    height:1.66
}

const info:IPerson = p

调用签名

格式:(参数列表):返回值

开发中如何选择:

  1. 只是描述函数类型本身(函数可以被调用,使用函数表达式)
  2. 函数作为对象可以被调用,同时也有其他属性,使用函数调用签名
interface IBar {
    name: string
    age: number
    (n1: number): number   // 调用签名
}

const bar: IBar = function (n1: number) {
    return 123
}
bar.name = 'ww'
bar.age = 12
bar(123)

构造签名(了解)

class Person {} // Person是类也是构造函数

interface IPerson {
    new () : Person
}
function foo(fn:IPerson) {
    return  new fn()
}
foo(Person)

参数的细节

可选参数的类型:指定类型 | undefined

有默认值的情况,类型注解可以省略,可以接受一个undefined的值

function foo(x=100, y?: number) { // 默认值、可选类型
    if (y !== undefined) {
        console.log(y + 10);
    }
}

foo(12)
foo(42,undefined)
foo(12, 32)

function bar(...arg:any[]) {} // 剩余参数
bar(12,'ww')

函数的重载(了解)

表示函数可以以不同的方式进行调用

// 1. 编写重载签名
function add(arg1: number, arg2: number): number
function add(arg1: string, arg2: string): string

// 2. 编写通用的函数实现
function add(arg1: any, arg2: any): any {
    return arg1 + arg2
}

add(12, 21)
add('ww', 'qq')
add({name: 'www'}, {age: 18}) // 有实现体的函数不能被直接调用

函数的重载-联合类型对比

可以使用联合类型,尽量使用联合类型实现

// 1. 函数的重载
function getLength(arg:string):number
function getLength(arg:any[]):number
function getLength(arg) {
    return arg.length
}
getLength('123')
getLength(['aa','bb','cc'])


// 2. 联合类型实现
function getLength1(arg:string | any[]) {
    return arg.length
}
getLength1('123')
getLength1(['aa','bb','cc'])

this类型

在没有对this进行特殊配置的情况下,this是any类型

"noImplicitThis": ture this在上下文不能正确的推导,必须明确指定

  1. 作为第一个参数,名字必须加this
  2. 后续传入的参数是从第二个开始,编译出来的代码,this类型会被抹除

tsc --init 初始化配置文件

function foo() {
    console.log(this); // 需配置"noImplicitThis": false
}

function bar(this:{name:string},info:{name:string}) {
    console.log(this, info);
}
bar.call({name:'wqq'},{name:'zzz'})

this的内置工具

function bar(this: { name: string }, info: { name: string }) {
    console.log(this, info);
}
type BarType = typeof bar

// 1. ThisParameterType: 获取BarType中this的类型
type BarThisType = ThisParameterType<BarType> 

// 2. OmitThisParameter: 删除this类型,剩余的函数类型
type PureType = OmitThisParameter<BarType> 

// 3. ThisType: 用于绑定一个上下文的this
interface IState {
    name: string,
    age: number
}
interface IStore {
    state: IState,
    eating: () => void
}
const store: IStore & ThisType<IState>= {
    state: {
        name: 'www',
        age: 21
    },
    eating: function () {
        console.log(this.name);
    }
}

面向对象

class Person {
    // 要声明成员属性
    name!: string // 不需要初始化
    age = 0

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}

// 实例对象:instance
const p1 = new Person('ww', 21)
const p2 = new Person('qq', 12)

成员修饰符

class Person {
    protected name: string // protected:仅在类的自身和子类中可见,受保护的属性和方法
    private age : number // private:私有的,在类的内部才能访问
    readonly message:string  // 只读属性

    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    public eating() { // public:公有的,默认就是公有的
        console.log('哈哈哈');
    }
}

参数属性 (语法糖)

把一个构造函数的参数转成一个同名同值的类属性

class Person {
    // 语法糖
    constructor(public name: string,public age: number) {
        this.name = name
        this.age = age
    }
}

抽象类 abstract

没有具体实现的方法(没有方法体)就是抽象方法,抽象方法不能被实例化

  1. 抽象方法,必须存在于抽象类中
  2. 抽象类是使用abstract声明的类

抽象类的特点:

  1. 抽象类是不能被实例的话(也就是不能通过new创建)
  2. 抽象类可以包含抽象方法,也可以包含有实现体的方法
  3. 抽象方法的类,必须是一个抽象类
  4. 抽象方法必须被子类实现,否则该类必须是一个抽象类
abstract class Shape {
    abstract getArea()
}

class Circle extends Shape {
    constructor(public radius: number) {
        super();
    }
    getArea() {
        return this.radius ** 2 * Math.PI
    }
}
// 通用函数
function calcArea(shape:Shape) {
    return shape.getArea()
    }
calcArea(new Circle(12))

ts对于类型检测使用的是鸭子类型:只关心属性和行为,不关心你具体对应的类型

class Person {
    constructor(public name: string, public age: number) {}
}
class Dog {
    constructor(public name: string, public age: number) {}
}

function printPerson(p:Person) {
    console.log(p.name,p.age);
}
printPerson(new Person('wqq',12))
printPerson({name:'qqq',age:12}) // 创建了字面量,将字面量赋值给了Person
printPerson(new Dog('旺财',2))

类具有的特性

  1. 可以创建类对应的实例
  2. 可以作为这个实力的类型
  3. 也可以当作有构造签名的函数

索引签名(了解)

interface ICollection {
    [index: string]: number // 索引签名
    length: number
}

接口被类实现

interface qq {
    name: string
    age: number
}
// 1.直接作为类型
const p: qq = {
    name: 'wqq',
    age: 21
}
// 2.接口被类实现
class Person implements qq {
    name: string
    age: number
}

枚举类型

enum Direction {
    TOP,  // 可设置值 TOP = 'top'
    LEFT,
    RIGHT
}
const d1:Direction = Direction.TOP

enum Color {
    red= 1 << 1
}



function turnDirection(direction:Direction) {
    switch (direction) {
        case Direction.TOP:
            console.log('向上移动');
            break
    }
}
// 监听键盘点击
turnDirection(Direction.TOP)

泛型

将类型参数化的过程

function useState<Type>(initialState: Type): [Type, (newValue: Type) => void] {
    let stateValue = initialState
    function setValue(newValue: Type) {
        stateValue = newValue
    }
    return [stateValue, setValue] // 返回元组
}

const [count, setCount] = useState(10)
const [banner, setBanner] = useState<any[]>([])

可以传入多个类型

function foo<T,E>(arg1:T,arg2:E) {}
foo(12,21)
foo('aa',12)
image.png

泛型接口

interface Person<T = number> { // 设置默认值
    name: T,
    age: number
}

const qq: Person<string> = {
    name: 'wqq',
    age: 21
}
const ww: Person = {
    name: 21,
    age: 21
}

泛型类

class Point<T = number> {
    x:T
    y:T
    constructor(x:T,y:T) {
        this.x = x
        this.y = y
    }
}

const p1 = new Point(12,21)
const p2 = new Point('32','42')

泛型约束

interface TLength {
    length: number
}

// 没有必要使用泛型
function getLength(arg: TLength) {
    return arg.length
}
getLength('1213')
getLength(['aa', 'bb', 'cc'])
getLength({length: 1222})

// 获取传入的内容,这个内容必须有length属性
// Type相当于是一个变量,用于记录本次调用的类型,所以在整个函数的执行周期中,一直保留着参数的类型
function getInfo<T extends TLength>(arg:T):T {
    return arg
}
getInfo('1213')
getInfo(['aa', 'bb', 'cc'])
getInfo({length: 1222})

传入的类型,obj当中的key的其中之一

function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
    return obj[key]
}

const info = {
    name: 'ww',
    age: 21,
    height: 1.66
}

const names = getObjectProperty(info, 'age')

映射类型(了解)

基本使用

interface IPerson {
    name:string
    age:number
}

type MapType<T> = {
    // [property in keyof T] :boolean
    readonly [property in keyof T]?: T[property] // 拷贝的同时变成了既是可选又是只读的
}
 type NewPerson = MapType<IPerson>

模块化

对babel、swc或者esbuild更好的提示,告诉它们什么样的导入可以被安全的移除

// 可以使用type前缀,表明是一个类型导入
import {type IDType, type IPerson} from "./type.ts";
import type {IDType, IPerson} from "./type.ts";

命名空间

export namespace price {
    function format(price) {
        return '$' + price
    }
}

自定义类型声明

declare const names:string

declare function foo(): void

declare class Person {
    name:string
    age:number
    constructor(name:string,age:number)
}

// 声明模块
declare module 'lodash' {
    export function join(args:any[]) :any;
}

// 声明文件模块
declare module '*.png'
declare module '*.jpg'
declare module '*.svg'

// 类型声明
declare namespace $ {
    export function ajax(settings: any): any;
}

类型分发

当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成分发

type toArray<T> = T extends any? T[]: never

type NuumArray = toArray<number>

type NumAndStrArray = toArray<number|string> // 分发类型: toArray<number | string>

类型体操

interface Hello {
    name: string,
    age: number,
    message?: string
}


type p1 = Partial<Hello> // 1. Hello都变成可选的


type p2 = Required<Hello> // 2. Hello都变成必选的

type p3 = Readonly<Hello>

type t1 = '成都' | '上海'
type p4 = Record<t1, Hello>
const person: p4 =
        {
            '成都': {
                name: 'xx',
                age: 10
            },
            '上海': {
                name: 'xxx',
                age: 20
            }
        },


    // 1.1类型体操
    type WQPartial<T> = {
        [P in keyof T]?: T[P]
    }

    // 1.2类型体操
    type WQRequired<T> = {
        [P in keyof T]-?: T[P]
    }

    // 1.3类型体操
    type WQReadonly<T> = {
        readonly [P in keyof T]: T[P]
    }