TypeScript
JavaScript 的基本知识
Atwood 定律:stack overflow 的创立者之一的 jeff Atwood 提出:任何可以使用 JavaScript 来实现的应用都最终会使用 JavaScript 实现。
- web端:JavaScript
- 移动端:reactNative、weex、uniapp 等框架实现跨平台开发
- 小程序端:离不开 JavaScript
- 桌面端:借助 electron 来开发
- 服务器端:借助 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 种方式来解决:
-
通过 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
-
安装 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” 称为类型保护,常见的类型保护:
-
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) } } -
平等缩小,比如
===、!== -
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) -
in
type Dog = { running: () => {} } type Fish = { swimming: () => {} } function walk(animal: Dog | Fish) { if ("swimming" in animal) { animal.swimming() } else { animal.running() } } -
等等
函数详解
函数的类型
// 函数作为参数时
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。。。
区别:
- interface可以重复的定义相同接口,而相同的interface会合并里面的属性。
- 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查找类型的位置:
-
内置类型声明:ts自带的
-
外部定义类型声明:可以去GitHub上安装对应的类型声明文件。可以使用该网站进行相关类型的查询
https://www.typescriptlang.org/dt/search?search= -
自己定义类型:编写.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大神新课》课程而来。