TypeScript语法介绍

217 阅读2分钟

TypeScript

官网:www.typescriptlang.org/

JavaScript 的基本知识

Atwood 定律:stack overflow 的创立者之一的 jeff Atwood 提出:任何可以使用 JavaScript 来实现的应用都最终会使用 JavaScript 实现。

  1. web端:JavaScript
  2. 移动端:reactNative、weex、uniapp 等框架实现跨平台开发
  3. 小程序端:离不开 JavaScript
  4. 桌面端:借助 electron 来开发
  5. 服务器端:借助 node 环境使用 JavaScript 开发

痛点:ES5 之前使用的 var 关键字关于作用域的问题、最初 JavaScript 设计的数组类型并不是连续的内存空间、至今(2021-10-10)也没有加入类型检测这一机制。

错误越早发现越好:开发 → 测试 → 上线

类型思维的缺失:前端人员通常不会对参数类型有思考,也不关心参数的类型,当需要确定类型时,则需要添加很多的逻辑判断。

2014年,Facebook 推出了 flow 来对 JavaScript 进行类型检测,同年微软推出了 typescript1.0 来进行检测。如今大部分都使用 typescript 来进行类型检测,并且 vue3.x 已经使用了 typescript 进行了重构。

基本知识

定义:

GitHub:TypeScript is a superest of javscript that compiles to clean javascript output.

Typescript:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

翻译:TypeScript 是拥有类型的 JavaScript 超集,它可以编译成普通、干净、完整的 JavaScript 代码。

理解:支持 JavaScript 所拥有的特性,紧紧跟进 ES 的标准、增加了类型约束,包括一些语法的扩展,比如枚举类型和元组类型等、ts 最终会编译成 js 代码,编译时也不用借助 babel 等工具。

众多的项目使用 ts:angular 源码、vue3 源码、vscode 编辑器、react 的 ant-design 的 ui 库、小程序也支持 ts 开发。

基本语法

原始步骤

用 tsc (typescript compiler) 来将 ts 代码编译为 js 代码:

安装 tsc(2021-10-10 v4.4.3):

npm install typescript -g

编写代码

let msg: string = "hello world"

function foo(payload: string) {
    console.log(payload.length)
}
foo("har")

// 直接报错
// foo()
// foo(123)

运行代码(会生成一个转换后的 js 文件)

tsc fileName

高级步骤

但执行该代码很麻烦,可以使用以下 2 种方式来解决:

  1. 通过 webpack 搭建环境(项目开发)

    npm init#进入指定目录,生成package.json
    npm install webpack webpack-cli -D
    

    编写 package.json

    "build": "webpack"
    

    安装 loader 来处理 ts 文件(2021-10-11 ts-loader v9.2.6 typescript v4.4.3)

    npm install ts-loader typescript -D
    

    配置 ts 的配置文件 tsconfig.js

    tsc --init
    

    编写 webpack.config.js

    const path = require("path")
    module.exports = {
      //mode配置必须要写,不然浏览器会警告,而且运行不了
      	mode:"development",
        entry: "./src/main.ts",
        output: {
            path: path.resolve(__dirname, "./dist"),
            filename: "bundle.js"
        },
        resolve: {
          //ts是便于解析,js等是为了解析
            extensions: [".ts",".js"]
        },
        module: {
            rules: [{
                test: /\.ts$/,
                loader: "ts-loader"
            }]
        }
    }
    

    实时刷新页面,即热更新

    npm install webpack-dev-server -D #v4.3.1
    npm install html-webpack-plugin -D #v5.3.2
    

    编写 package.json

    "serve": "webpack serve"
    

    运行即可:npm run serve

  2. 安装 ts-node,代码跑在 node 环境(练习使用).ts-node 依赖以下 2 个包(tslib v2.3.1@types/nnode v16.10.3

    npm install ts-node -g
    npm install tslib @types/node -g
    ts-node fileName
    

js 变量的声明

定义变量时,必须要指定标识符的类型。

var / let /const 标识符:数据类型 = 值

// 基本用法
var name: string = "xdyy"
let age: number = 10
const height: number = 1.55
// string:typescript的字符串类型
// String:JavaScript的字符串包装类的类型
const msg: String = "hello world"
// 没有类型注解,但是ts会进行类型推导。赋值数字时也是会报错的
let foo = "xx"
export { }

数组类型:

// names是一个数组类型,但是数组内存放什么类型的数据是不确定的
// const names = []

// 数组中存放不同的数据
// names.push("abc")
// names.push(123)

// 声明一个字符串数组,该方法不推荐,react、jsx中是有冲突的
const names: Array<string> = []
// 该方法更推荐
const names2: string[] = []

对象类型:

// 该类型会自动推导
const info = {
    name: "harr",
    age: 18,
}

const info1: object = {
    name: "harr",
    age: 18,
}
// 编译都会报错,所以尽量使用类型推断
console.log(info1.name)

any 类型

// 随便类型就可以
let msg: any = "hello world"
msg = 123
msg = true
// 使用any的情况
// 1.当进行一些类型断言as
// 2.不想写数据类型时
// 3.暂时确定不了数据类型时

unknown 类型

function foo() {
    return "abv"
}
function bar() {
    return 123
}
// unknown类型只能赋值给any和unknown类型
// any类型可以赋值给任意类型
let flag = true
let result: unknown
if (flag) {
    result = foo()
} else {
    result = bar()
}

never 类型

永远不会有返回值的。常见应用场景如下

  • Unreachable code 检查:标记不可达代码,获得编译提示。
  • 类型运算:作为类型运算中的最小因子。
  • Exhaustive Check:为复合类型创造编译提示。
function handleMsg(msg: number | string | boolean) {
    switch (typeof msg) {
        case "number":
            console.log("number--")
            break
        case "string":
            console.log("string--")
            break
        case "boolean":
            console.log("boolean--")
            break
        default:
        // 在判断的最后,将check设置为一个never类型,那么将msg赋值给check的时候就会报错,则提醒我们添加对应的处理代码
            const check: never = msg
    }
}

handleMsg("123")
handleMsg(23)
// Boolean值不行,那么就去函数中添加对应的类型,但是我们并没有对Boolean值做出对应的处理
handleMsg(true)

元组类型 tuple

多种元素的组合。

const info: any[] = ["har", 18, 1.88]
// 取出的name是any类型
const name = info[0]
console.log(name.length)
// 元组类型
const info1: [string, number, number] = ["harr", 12, 13]
// 可以确定name1的类型
const name1 = info1[0]
console.log(name1.length)
export { }

应用场景:

// tuple作为函数的返回值很方便
function useState<T>(state: T) {
    let currentState = state
    const changeState = (newState: T) => {
        currentState = newState
    }
    const tuple: [T, (newState: T) => void] = [currentState, changeState]
    return tuple
}

// 元组类型可以确定具体的类型,使用泛型T可以传入不同的类型
const [counter, setCounter] = useState(10)
const [title, setTitle] = useState("10")
export { }

对象类型、可选类型、联合类型、类型别名

// 对象的分隔  分号、逗号都可以
function printPoint(point: { x: number, y: number }) {}
// 可选类型的本质是 类型/undefined 的联合类型,只不过联合类型的undefined必须插进去
function printPoint(point: { x: number, y: number, z?: number }) {}
// union type,如果类型中有一个是函数,需要用括号括起来
function printId(id: number | string) {}
type PointType = {
    x: number
    // 是否写逗号都是可以的
    y: number,
    z?: number
}
function printPoint(point: PointType) {}

类型断言 as

有时候 ts 无法获取具体的类型信息,此时就需要使用类型断言。将范围缩小,指定确定的类型。

// const el: HTMLElement = document.getElementById("xdyy")
const el = document.getElementById("xdyy") as HTMLImageElement

常见应用场景

// <img id="xdyy" />
// const el: HTMLElement = document.getElementById("xdyy")
const el = document.getElementById("xdyy") as HTMLImageElement
el.src = "url"
class Person {}
class Student extends Person {
    studing() { }
}
function sayHello(p: Person) {
    (p as Student).studing()
}
const stu = new Student()
sayHello(stu)
const msg = "hello"
// const num: number = msg
const num: number = msg as any as number

非空类型断言 !

确定该参数不为空

function printMsg(msg?: string) {
    console.log(msg!.length)
}

可选链

是 ES11(2020)中增加的特性,操作符:?.

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

type Person = {
    name: string,
    firends?: {
        name: string,
        age?: number
    }
}
const info: Person = {
    name: "harr",
    firends: {
        name: "xdyy"
    }
}
console.log(info.name)
console.log(info.firends?.name)
// age后面的可选链可以不写
console.log(info.firends?.age)
export { }

!!??运算符

// !!将其他类型转换为Boolean类型
let msg = "harr"
let msg1 = !!msg

//?? 运算符,空值合并操作符。
let msg2: string | null | undefined = "sdhs"
// msg2没有值则取后面的默认值
const msg3 = msg2 ?? "harr"
console.log(msg3)

export { }

字面量类型

// const msg: string = "hello world"
// 等价于
const msg: "hello world" = "hello world"

// 字面量通常结合联合类型使用
type Alignment = "left" | "right" | "center"
// 在写align的值时会有提示,类似于枚举类型
let align: Alignment = "right"

字面量推理:

type Methods = "POST" | "GET"
function request(url: string, method: Methods) { }
const options = {
    url: "www.baidu.com.harr",
    method: "POST"
}
// 将options.method作为参数传入request函数会报错,因为options.method的类型推导为字符串
// 但是request函数要求的参数类型是Methods
// request(options.url, options.method)
// 解决方法一:
// type Option = {
//     url: string,
//     method: Methods
// }
// 解决方法二:
// request(options.url, options.method as Methods)
// 解决方法三:
// const options = {
//     url: "www.baidu.com.harr",
//     method: "POST"
// } as const

类型缩小 type narrowing 及保护

在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称为缩小。

typeof padding === “number” 称为类型保护,常见的类型保护:

  1. typeof

    function printId(id: number | string) {
        // id为联合类型
        console.log(id)
        if (typeof id === "string") {
            // id为string类型
            console.log(id.toUpperCase())
        } else {
            // id为number类型
            console.log(id)
        }
    }
    
  2. 平等缩小,比如===、!==

  3. instanceof

    class Student {
        studing() { }
    }
    class Teacher {
        teaching() { }
    }
    function work(person: Student | Teacher) {
        if (person instanceof Student) {
            person.studing()
        } else {
            person.teaching()
        }
    }
    const harr = new Student()
    work(harr)
    
  4. in

    type Dog = {
        running: () => {}
    }
    type Fish = {
        swimming: () => {}
    }
    function walk(animal: Dog | Fish) {
        if ("swimming" in animal) {
            animal.swimming()
        } else {
            animal.running()
        }
    }
    
  5. 等等

函数详解

函数的类型

                                                                                                                                                                                                                                     // 函数作为参数时
function foo() { }
function bar(fn: () => void) {
    fn()
}
bar(foo)
// 定义常量
type AddType = (num1: number, num2: number) => number
const add: AddType = (num1, num2) => {
    return num1 + num2
}

参数默认值

// 有默认值尽量写在后面,如果写在前面,需要使用其默认值时需要传入参数undefined
// 一般顺序:必传类型→有默认值类型→可选类型
function foo(x: number, y: number = 44) {
    console.log(x, y)
}
foo(14)
export { }

参数的剩余参数

// 剩余参数必须放在最后
function sum(num1: number, ...nums: number[]) {
    let total = num1
    for (const num of nums) {
        total += num
    }
    return total
}

可推导的this类型

function eating1() {
    console.log(this.name + " eating")
}
const info1 = {
    name: "xdyy",
    // 会报错,this不能推导出来
    eating1: eating1
}
// 解决方法
type ThisType = { name: string }
function eating1(this: ThisType) {
    console.log(this.name + " eating")
}

info1.eating1()

函数重载

函数名称相同,但是参数不同,包括数量和类型都算。

// 重载函数
function add(a1: number, a2: number): number;
function add(a1: string, a2: string): string;
// 函数实现体
function add(a1: any, a2: any) {
    return a1 + a2
}
const result = add(10, 20)
// 该写法匹配不到任何一个重载函数,所以会报错
// const result1 = add(12, "sdsd")
export { }
//方便通过联合类型来书写的函数尽量使用联合类型来写

类的使用

es6开始,引入了class关键字。类具有很强大的封装性。

类的定义

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()
const p = new Person("harr", 12)

p.eating()

类的继承

class Student extends Person {}

类的成员修饰符

默认就是public、private只能内部访问、protected内部及子类可见

private name: string

只读属性

class Person {
    readonly name: string
    age?: number
    readonly friends?: Person
    constructor(name: string, firends?: Person, age?: number) {
        // 只读属性赋值之后就不可以再修改了
        this.name = name
        this.friends = firends
        this
    }
}
const p = new Person("harr", new Person("xdyy"), 6)
console.log(p.name)
if (p.friends) {
    // firends是只读属性,是指不能直接给firends赋值,但是其内部的属性可以进行修改
    p.friends.age = 45
    console.log(p.friends.age)
}

抽象类abstract

抽象函数必须再抽象类里面。抽象类不能被实例化。

function makeArea(shape: Shape) {
    return shape.getArea()
}
abstract class Shape {
    // 抽象函数只能写在抽象类中,且可以没有实现体
    abstract getArea(): number
}
// 继承自Shape的话则要求必须有getArea函数
class Rectangle extends Shape {
    private width: number
    private height: number
    constructor(width: number, height: number) {
        // 只要该类有父类,则必须在构造函数里调用super方法
        super()
        this.width = width
        this.height = height
    }
    getArea() {
        return this.width * this.height
    }
}
class Circle extends Shape {
    private r: number
    constructor(r: number) {
        super()
        this.r = r
    }
    getArea() {
        return this.r * this.r * 3.14
    }
}
const rectangle = new Rectangle(10, 2)
const circle = new Circle(4)
console.log(makeArea(rectangle))
console.log(makeArea(circle))

// 不写成抽象类的话可以给makeArea传入任何的值
// makeArea("harrr")
// 抽象类不能实例化
// makeArea(new Shape())

类的类型

class Person {
    name: string = "234"
    eating() { }
}
const p = new Person()
const p1: Person = {
    name: "harr",
    eating() { }
}
function action(p: Person) {
    console.log(p.name)
}

action(new Person())
action({ name: "xdyy", eating: function () { } })
export { }

interface接口的使用

声明对象类型

// 通过类型别名来声明对象
// type InfoType ={name: string, age: number}
// 通过接口来说明对象类型
interface InfoType {
    name: string
    age: number
}
const info: InfoType = {
    name: "why",
    age: 13
}
// 可选类型、可选链 等,同函数写法

索引类型

interface IndexLanguage {
    [index: number]: string
}
const frontLanguage: IndexLanguage = {
    0: "html",
    1: "css",
    2: "js",
    3: "vue",

}

函数类型

// type写法
// type CalcFn = (n1: number, n2: number) => number
// interface写法
interface CalcFn {
    (n1: number, n2: number): number
}

interface和type的区别

相同:都可以用来定义对象类型。如果是定义非对象类型,通常推荐使用type。type=xxx | xxx。。。

区别:

  1. interface可以重复的定义相同接口,而相同的interface会合并里面的属性。
  2. type定义的是别名,不能重复赋值。

命名空间

namespace time {
    export function format(time: string) {
        return "2001-5-7"
    }
}
namespace price {
    // 要在外部使用的话则需要export
    export function format(price: number) {
        return "444"
    }
}
// time.format("time")

枚举类型

定义一组常量然后放在一个类型中。

enum Direction {
  //内部实际是有值的,默认从0开始,可以改变其值
    LEFT,
    RIGHT,
    TOP,
    BOTTOM
}
function turnDirection(direction: Direction) { }
turnDirection(Direction.LEFT)

泛型

将类型参数化,使使用者可以自己觉得参数的类型。尽可能可以复用的代码。

基本使用

function sum<T>(xxx: T) {
    return xxx
}
sum<number>(10)
sum<string>("harr")
sum<any[]>([1, 2])
// 返回值是字面量类型
sum(44)

多个参数

// T:type
// K:key\value
// E:element
function foo<T, H>(n1: T, n2: H) { }
// function foo<T, H>(n1: H, n2: H) {}

类型的查找

.d.ts文件,它是用来做类型的声明(declare),它仅仅用来做类型检测。

ts查找类型的位置:

  1. 内置类型声明:ts自带的

  2. 外部定义类型声明:可以去GitHub上安装对应的类型声明文件。可以使用该网站进行相关类型的查询

    https://www.typescriptlang.org/dt/search?search=
    
  3. 自己定义类型:编写.d.ts文件即可

    // 声明模块
    declare module "lodash" {
        function join(arg: any[]): void
    }
    // 声明变量、函数、类
    declare let harName: string
    declare let harAge: number
    declare function harFoo(): void
    declare class Person {
        name: string
        age: number
        constructor(name: string, age: number)
    }
    // 声明文件
    declare module "*.jpg"
    // 声明命名空间
    declare namespace $ {
        export function aaa(settings: any): any
    }
    

    ps:本笔记根据coderwhy老师的《深入Vue3+TypeScript技术栈-coderwhy大神新课》课程而来。