目录
一、typescript语法精讲(环境)
1、ts初体验
- IDE语法检测原理
IDE内部会生成代码的AST树,从而分析语法的错误
- 安装和编译ts
# 安装
npm i -g typescript
# 编译
tsc index.ts
- ts编译的作用域
* 默认情况下所有ts文件都是在同一作用域下编译的。所以如果存在相同变量名,则编译会有冲突
* 解决冲突方式一:ts文件底部加【export {}】,表示该文件是一个模块(模块有自己的作用域)
- ts-node搭建ts环境
# 安装
npm i -g ts-node
# 安装依赖
npm i -g tslib @types/node
# 编译并在node环境运行
ts-node index.ts
2、webpack搭建ts环境
- 安装
npm init -y
npm i -D webpack webpack-cli
npm i -D ts-loader typescript
# 生成tsconfig.json文件
tsc --init
npm i -D webpack-dev-server
npm i -D html-webpack-plugin
- package.json
{
"name": "learn-ts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"serve": "webpack serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.9",
"typescript": "^4.6.4",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.8.1"
}
}
- webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode: "development",
entry: "./src/main.ts",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "bundle.js"
},
devServer: {},
resolve: {
extensions: [".ts", ".js", ".cjs", ".json"]
},
module: {
rules: [{
test: /\.ts$/,
loader: "ts-loader"
}]
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
})
]
}
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ts</title>
</head>
<body>
</body>
</html>
- main.ts
import {sum} from "./math"
const message: string = "黄婷婷"
console.log(sum(20, 30))
console.log(message)
- math.ts
export function sum(num1: number, num2: number) {
return num1 + num2
}
二、typescript语法精讲(类型)
1、ts变量的定义格式
/**
* 1、变量的声明:
* - var/let/const 标识符: 数据类型(类型注解) = 赋值
* 2、tslint(ts代码规范检查工具):
* - 安装:npm i -g tslint
* - 生成tslint.json:tslint --init
* 3、基本数据类型和基本数据类型的包装类:
* - string:Typescript中的字符串类型
* - String:Javascript的字符串包装类的类型
* 4、类型推断/推导:
* - 默认情况下进行赋值时,会将赋值的值的类型,作为前面标识符的类型
*/
const name: string = "黄婷婷"
const age = 18
export {}
2、javascript类型
- number类型
let num1: number = 100// 十进制
let num2: number = 0b100// 二进制
let num3: number = 0o100// 八进制
let num4: number = 0x100// 十六进制
- boolean类型
let bol1: boolean = true
let bol2: boolean = 1 > 2
- string类型
let str1: string = "黄婷婷"
let str2 = `name:${str1}`
- Array类型
let persons1: Array<string> = []// 不推荐(与jsx有冲突)
let persons2: string[] = []
- object类型
// 尽量类型推导(object类型不要使用,访问属性会报错)
let obj: object = {
name: "黄婷婷",
age: 18
}
- null和undefined类型
// null和undefined默认推导类型为any
let v1: null = null
let v2: undefined = undefined
- symbol类型
const title1 = Symbol("黄婷婷")
const title2 = Symbol("黄婷婷")
const obj = {
[title1]: "孟美岐",
[title2]: "姜贞羽"
}
3、typescript类型
- any类型
// 类型断言:as any
let message: any = "黄婷婷"
message = 18
- unknown类型
/**
* 1、any和unknown区别
* - unknown类型只能赋值给any和unknown类型
* - any类型可以赋值给任意类型(除了never类型)
*/
let message: unknown = "黄婷婷"
let text: unknown = message
- void类型
function foo(): void {
// return null或者undefined或者不return(可以返回任意类型)
}
- never类型
function foo(): never {
while (true) {
}
}
function bar(): never {
throw new Error("")
}
// 1、never类型应用场景
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case "string":
break
case "number":
break
case "boolean":
break
default:
const check: never = message
}
}
- tuple类型(元组类型)
let person: [string, number] = ["黄婷婷", 18]
let name: string = person[0]
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(18)
setCounter(20)
const [title, setTitle] = useState("黄婷婷")
const [flag, setFlag] = useState(true)
type MyFunction = () => void
const foo: MyFunction = () => {
}
export {}
4、typescript类型补充
- 函数的参数和返回值类型
/**
* 1、参数加类型注解
* 2、返回值加类型注解
* 3、在开发中,通常情况下可以不写返回值的类型(自动推导)
*/
function sum(num1: number, num2: number): number {
return num1 + num2
}
- 匿名函数的参数类型
// 通常情况下,在定义一个函数时,都会给参数加上类型注解的
function foo(message: string) {
}
const names = ["黄婷婷", "孟美岐", "姜贞羽"]
// item的类型可以根据上下文环境推导出来,这个时候可以不添加类型注解
// 上下文中的函数:可以不添加类型注解
names.forEach(item => {
console.log(item.split(""))
})
- 对象类型
// 对象类型属性之间可以,或者;隔开
function printPoint(point: { x: number, y: number; z: number }) {
console.log(point.x)
console.log(point.y)
console.log(point.z)
}
- 可选类型
function printPoint(point: { x: number, y: number, z?: number }) {
console.log(point.x)
console.log(point.y)
console.log(point.z)// 不传则为undefined
}
- 联合类型
function printId(id: number | string | boolean) {
// 类型缩小(type narrow)
// 类型保护(type guard)
if (typeof id === "string") {
// typescript可根据类型判断语句确定,id一定是string类型
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
- 可选类型和联合类型的关系
// 参数是可选类型的时候,可以传undefined
function foo(message?: string) {
console.log(message)
}
// 参数不可以省略
function bar(message: string | undefined) {
console.log(message)
}
- 类型别名
// type用于定义类型别名(type alias)
type IDType = string | number | boolean
type PointType = {
x: number
y: number
z?: number
}
function printId(id: IDType) {
}
function printPoint(point: PointType) {
}
- 类型断言as
// 1、类型断言as
const el = document.getElementById("img") as HTMLImageElement;
el.src = ""
/**
* 2、类似于java中的强制类型转换
* 伪代码:Student extends Person;person as Student
*/
/**
* 3、string转number
* 伪代码:string as any as number
*/
- 非空类型断言!
function printMessageLength(message?: string) {
// 当没有tsconfig.json文件的时候(ts-node默认会有此文件),不加!编译依然能通过
console.log(message!.length)
}
- 可选链的使用
type Person = {
name: string,// 不加,或;,则必须得换行
friend?: {
name: string
}
}
const person: Person = {
name: "黄婷婷",
friend: {
name: "孟美岐"
}
}
/**
* 1、当对象的属性不存在时,会短路,直接返回undefined
* 2、语法是?.,所以不能name?
* 3、?.不能在=左边
*/
console.log(person.friend?.name)
- !!和??的作用
// 1、!!
const message = "黄婷婷"
// 相当于:Boolean(message)
console.log(!!message)
// 2、??(只判断undefined和null)
const flag = undefined
const content = flag ?? "孟美岐"
console.log(content)
- 字面量类型
// 1、"黄婷婷"也是可以作为类型的,叫做字面量类型
// 相当于:const message = "黄婷婷"
let message: "黄婷婷" = "黄婷婷"
// 2、字面量类型的意义,就是必须结合联合类型
type Alignment = "left" | "right" | "center"
let align: Alignment = "left"
- 字面量推理
type Method = "GET" | "POST"
function request(url: string, method: Method) {
}
type Request = {
url: string,
method: Method
}
// 方式一:const options: Request
// 方拾二:
// 默认情况下url和method属性为string类型,后面加as const则变为字面量类型
const options = {
url: "https://www.dingcaiyan.com",
method: "POST"
} as const
request(options.url, options.method)
export {}
- 类型缩小
/**
* 1、常见的类型保护
* - typeof
* - 平等缩小(比如===、!==)
* - instanceof
* - in
*/
// 案例一:typeof
type IDType = number | string
function printID(id: IDType) {
if (typeof id === "string") {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
// 案例二:平等缩小(比如===、!==)
type Direction = "left" | "right"
function printDirection(direction: Direction) {
// if else/switch case语句
switch (direction) {
case "left":
console.log(direction)
break;
default:
console.log(direction)
}
}
// 案例三:instanceof
function printTime(time: number | Date) {
if (time instanceof Date) {
// cookie使用的是GMT(格林威治时间:GMT=UTC+0)时间
// 北京时间:CST=UTC+8
console.log(time.toUTCString())// 世界标准时间
} else {
console.log(time)
}
}
// 案例四:in
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
function walk(animal: Fish | Dog) {
if ("swimming" in animal) {
animal.swimming()
} else {
animal.running()
}
}
const fish: Fish = {
swimming() {
console.log("swimming")
}
}
walk(fish)
三、typescript语法精讲(函数)
1、typescript函数类型
- 概念
// 在javascript中函数是一等公民。可以作为参数,也可以作为返回值
// 1、函数作为参数时,在参数中如何编写类型
function foo() {
}
type FooFnType = () => void
function bar(fn: FooFnType) {
fn()
}
bar(foo)
// 2、定义常量时,编写函数的类型(形参名称不能省)
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (num1: number, num2: number) => {
return num1 + num2
}
- 案例
function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
return fn(n1, n2)
}
const result1 = calc(20, 30, function (a1, a2) {
return a1 + a2
})
console.log(result1)
const result2 = calc(20, 30, function (a1, a2) {
return a1 * a2
})
console.log(result2)
2、参数的可选类型
// 可选类型是必须写在必选类型的后面的
function foo(x: number, y?: number) {
}
3、参数的默认值
// 参数顺序:必选参数, 有默认值的参数, 可选参数
function foo(x: number, y: number = 30) {
console.log(x, y)
}
foo(20, 40)// 20 40
// 有默认值的参数,也可以传undefined
foo(20, undefined)// 20 30
foo(20)// 20 30
4、函数的剩余参数
function sum(totalNum: number, ...nums: number[]) {
let total = totalNum;
for (const num of nums) {
total += num;
}
return total;
}
console.log(sum(10, 20))
console.log(sum(10, 20, 30))
console.log(sum(10, 20, 30, 40))
5、this的默认推导
// this是可以被推导出来的,this指向person对象
const person = {
name: "黄婷婷",
study() {
console.log(this.name)
}
}
person.study()
6、this的不明确类型
type ThisType = { name: string };
function eating(this: ThisType, message: string) {
console.log(this.name + ":" + message);
}
const info = {
name: "黄婷婷",
eating: eating,
};
// 隐式绑定
info.eating("哈哈哈");
// 显示绑定
eating.call({name: "孟美岐"}, "呵呵呵")
eating.apply({name: "程潇"}, ["嘿嘿嘿"])
export {}
7、函数的重载
- 联合类型
/**
* 1、通过联合类型有两个缺点
* - 进行很多的逻辑判断(类型缩小)
* - 返回值的类型依然是不能确定
* 2、联合类型和函数重载的对比:开发过程中,能使用联合类型且更简单的情况下,尽量使用联合类型
*/
function add(a1: number | string, a2: number | string) {
if (typeof a1 === "number" && typeof a2 === "number") {
return a1 + a2
} else if (typeof a1 === "string" && typeof a2 === "string") {
return a1 + a2
}
// return a1 + a2
}
- 函数重载
// 函数的重载:函数的名称相同,但是参数不同的几个函数,就是函数的重载
function add(num1: number, num2: number): number;// 没函数体
function add(num1: string, num2: string): string;
function add(num1: any, num2: any): any {
if (typeof num1 === "string" && typeof num2 === "string") {
return num1.length + num2.length
}
return num1 + num2
}
const result1 = add(20, 30)
const result2 = add("黄婷婷", "孟美岐")
console.log(result1)
console.log(result2)
// 在函数的重载中,实现函数是不能直接被调用的
// add({name: "姜贞羽"}, {age: 18})
四、typescript语法精讲(类)
1、类的定义
/**
* 1、属性必须赋初始化值
* - 直接赋初始化值
* - 通过构造函数赋初始化值
*/
class Person {
name: string = "黄婷婷"
age: number
constructor(age: number) {
this.age = age
}
study() {
console.log(this.name, this.age)
}
}
const person = new Person(18);
console.log(person)
2、类的继承
class Person {
name: string
constructor(name: string) {
this.name = name
}
running() {
console.log("跑步")
}
}
class Student extends Person {
age: number
constructor(name: string, age: number) {
// super()调用父类的构造器(必须在使用this之前调用一次)
super(name);
this.age = age
}
running() {
super.running()
}
study() {
console.log("学习")
}
}
3、类的多态
class Animal {
action() {
console.log("animal action")
}
}
class Dog extends Animal {
action() {
console.log("dog running")
}
}
class Fish extends Animal {
action() {
console.log("fish swimming")
}
}
// 通过函数重载或联合类型(animals: (Dog | Fish)[])也能实现
function makeActions(animals: Animal[]) {
animals.forEach(animal => {
animal.action()
})
}
makeActions([new Dog(), new Fish()])
4、类的成员修饰符
/**
* 1、在typescript中,类的属性和方法支持三种修饰符
* - public(默认,可省略):任何地方可见
* - private:自身类中可见
* - protected:自身类和子类中可见
*/
class Person {
private name: string = "黄婷婷"
protected age: number = 18
printName() {
console.log(this.name)
}
}
class Student extends Person {
printAge() {
console.log(this.age)
}
}
const student = new Student();
student.printName()
student.printAge()
5、只读属性readonly
/**
* 1、只读属性是可以在构造器中赋值,赋值之后就不可以修改
* 2、属性本身不能进行修改,但是如果它是对象类型,对象中的属性是可以修改
*/
class Person {
readonly name: string
age: number
readonly friend?: Person
constructor(name: string, age: number, friend?: Person) {
this.name = name
this.age = age
this.friend = friend
}
}
const person = new Person("黄婷婷", 18, new Person("孟美岐", 19));
if (person.friend) {
person.friend.age = 20
}
console.log(person)
6、getter和setter
class Person {
private _name: string = "黄婷婷"
get name() {
return this._name
}
set name(newName) {
this._name = newName
}
}
const person = new Person();
person.name = "孟美岐"
console.log(person.name)
7、类的静态成员
class Person {
static username: string = "黄婷婷"
static study() {
console.log("学习")
}
}
console.log(Person.username)
Person.study()
8、抽象类abstract
/**
* 1、抽象方法必须在抽象类中
* 2、子类必须实现父类的抽象方法(extends)
* 3、抽象类不能new
* 4、tsconfig.json默认配置,抽象方法必须指定返回值类型
*/
abstract class Shape {
abstract getArea(): number
}
9、类的类型
class Person {
username: string = "黄婷婷"
study() {
console.log("学习1")
}
}
const p: Person = {
username: "孟美岐",
study() {
console.log("学习2")
}
}
五、typescript语法精讲(接口)
1、声明对象类型
// 1、类型别名type
/*
type InfoType = {
readonly name: string
age: number
friend?: {
name: string
}
}*/
// 2、接口interface
interface InfoType {
readonly name: string
age: number
friend?: {
name: string
}
}
const info: InfoType = {
name: "黄婷婷",
age: 18,
friend: {
name: "孟美岐"
}
}
console.log(info.friend?.name)
// info.name = "姜贞羽"// 只读属性不可修改
export {}
2、索引类型
interface IProp {
[propName: string]: any
}
const obj: IProp = {
name: "姜贞羽",
age: 18
}
interface IArr {
[index: number]: any
}
const arr = ["黄婷婷", "孟美岐", "鞠婧祎"]
3、函数类型
/**
* 1、type IPerson = { (username: string): boolean }
* 2、type IPerson = (username: string) => boolean
*/
interface IPerson {
(username: string): boolean
}
const person: IPerson = (username) => {
return true
}
4、接口的继承
interface ISwim {
swimming: () => void
}
interface IFly {
flying: () => void
}
// 接口支持多继承(extends),类不支持多继承
interface IAction extends ISwim, IFly {
}
const action: IAction = {
swimming() {
},
flying() {
}
}
5、交叉类型
type aType = number | string
type bType = number | boolean
type cType = aType | bType // number | string | boolean
type dType = aType & bType // number
type eType = number & string // never
interface ISwim {
swimming: () => void
}
interface IFly {
flying: () => void
}
type MyType1 = ISwim | IFly // 实现接口之一
type MyType2 = ISwim & IFly // 接口都得实现
const obj1: MyType1 = {
flying() {
}
}
const obj2: MyType2 = {
swimming() {
},
flying() {
}
}
6、接口的实现
interface ISwim {
swimming: () => void
}
interface IRun {
running: () => void
}
class Person implements ISwim, IRun {
running(): void {
}
swimming(): void {
}
}
7、interface和type区别
/**
* 1、使用推荐
* - 非对象类型使用type,比如联合类型、交叉类型、函数类型
* - 对象类型使用interface
* 2、区别
* - interface可以重复定义,属性和方法会合并
* - type不可以重复定义
*/
interface Person {
name: string
}
interface Person {
age: number
}
// type Person = { name: string, age: number }
const person: Person = {
name: "黄婷婷",
age: 18
}
8、擦除(freshness)
interface IPerson {
name: string
age: number
}
const person = {
name: "黄婷婷",
age: 18,
address: "无锡"
}
function printPerson(person: IPerson) {
console.log(person)
}
/**
* 1、将一个变量标识符赋值给其他的变量时(对象的引用赋值,对象字面量是不可以直接赋值的),
* 会进行freshness擦除操作。
* 2、多了属性address会被擦除,少了属性会报错类型不匹配
* 3、类型限制原因,函数中不可以使用address属性,但打印依然还是会有此属性
*/
printPerson(person)
六、typescript语法精讲(枚举、泛型、模块化、类型查找)
1、枚举类型
// type Direction = "left" | "right" | "center"
/**
* 1、枚举类型默认值从0开始递增
* 2、可赋字符串类型值
*/
enum Direction {
LEFT = 0,
RIGHT,// 1
CENTER// 2
}
// 3、类型判断了所有联合类型或枚举类型之后,default或else的类型为never
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
break
case Direction.RIGHT:
break
case Direction.CENTER:
break
default:
const foo: never = direction
}
}
2、泛型
- 类型参数化
/**
* 1、在定义这个函数时,我不决定这些参数的类型
* 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
*/
function printAny<Type>(arg: Type): Type {
return arg
}
// 明确指定类型
printAny<string>("黄婷婷")
printAny<number>(18)
// 类型推导(字面量类型)
printAny("黄婷婷")
printAny(18)
- 多个泛型
/**
* 1、泛型常用名称
* - T:type的缩写
* - K,V:key和value的缩写
* - E:element的缩写
* - O:object的缩写
*/
function foo<T, E>(a1: T, a2: T) {
}
- 泛型接口
interface IPerson<T1 = string, T2 = number> {
name: T1
age: T2
}
// 不能类型推导。可通过赋默认类型解决
const p: IPerson = {
name: "黄婷婷",
age: 18
}
- 泛型类
class Point<T> {
x: T
y: T
constructor(x: T, y: T) {
this.x = x
this.y = y
}
}
const p1 = new Point(255, 255)
const p2 = new Point<number>(255, 255)
const p3: Point<number> = new Point(255, 255)
- 泛型的类型约束
interface ILength {
length: number
}
function getLength<T extends ILength>(arg: T) {
return arg.length
}
getLength("黄婷婷")
getLength(["黄婷婷", "孟美岐", "鞠婧祎"])
getLength({length: 3})
3、模块化开发
- 命名空间namespace
// src/math.ts
export namespace time {
export function format(time: number) {
return "2022-05-06"
}
}
export namespace price {
export function format(price: number) {
return "99.99"
}
}
// src/main.ts
import {time, price} from "./math";
time.format(0)
price.format(0)
4、类型的查找
- 概念
* typescript文件介绍
- .ts:最终会输出.js文件,编写代码的地方
- .d.ts:用来做类型的声明(declare),仅做类型检测
* typescript查找类型声明
- 内置类型声明
- 外部定义类型声明
- 自己定义类型声明
- 内置类型声明
* 内置类型声明是typescript自带的,帮助我们内置了javascript运行时的一些标准化api的声明文件
- 包括比如Math、Date等内置类型,也包括dom api,比如window、document等
* 内置类型声明通常在我们安装typescript的环境中会带有的
- https://github.com/microsoft/Typescript/tree/main/lib
- 外部定义类型声明
* 外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明
* 这些库通常有两种类型声明方式
- 在自己库中进行类型声明(编写.d.ts文件),比如axios
- 通过社区的一个公有库DefinitelyTyped存放类型声明文件
该库的github地址:https://github.com/DefinitelyTyped/DefinitelyTyped/
该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search=
比如我们安装react的类型声明:npm i @types/lodash --save-dev
- 自己定义类型声明
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ts</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
const myVariable = "黄婷婷"
function myFunction() {
console.log("孟美岐")
}
class MyClass {
constructor(name, age) {
this.name = name
this.age = age
}
}
</script>
</body>
</html>
// src/main.ts
// 1、声明模块
import lodash from "lodash"
console.log(lodash.join(["黄婷婷", "孟美岐"]))
// 2、声明变量/函数/类
console.log(myVariable)
myFunction()
console.log(new MyClass("姜贞羽", 20))
// 3、声明文件
import gongping from "./img/gongping.png"
// 4、声明命名空间
$.ajax({})
// src/index.d.ts
// 1、声明模块
declare module "lodash" {
export function join(arr: any[]): void;
}
// 2、声明变量/函数/类
declare const myVariable: string
declare function myFunction(): void;
declare class MyClass {
name: string
age: number
constructor(name: string, age: number)
}
// 3、声明文件
declare module "*.png"
// 4、声明命名空间
declare namespace $ {
export function ajax(settings: any): any;
}
- 扩展全局变量
// 扩展全局变量
declare global {
interface Window {
defineFunctionName: () => void
}
}
// 即使此声明文件不需要导出任何东西,仍需要导出一个空对象
export {}