TypeScript快速学习笔记(上)
说明
- 强类型语言TS,实际上一直在用,虽然用的磕磕绊绊,不过这次因为是钱包,感觉类型等安全问题要重视,再学习一遍。
学习Up主的一个学习视频,bilibili.com/video/BV1XG411E7dg/?p=5&sp..,他学习的是ORelly的一本书。
《Learning TypeScript Enhance Your Web Development Skills Using Type-Safe JavaScript》,Josh Goldberg,O' Reilly,2022
- 以下是笔记,计划后面有点经验和体感了,给不熟悉前端的后端同学开个快速入门课程。
1 JS to TS
类型系统提示代码安全和稳定性
类型检查器分析代码
仅在开发环境下的类型注释:何时添加
TS同IDE结合重构代码
TS内建推断和新语法来表示复杂类型
95年Brendan Eich,10天设计了JS
Show me the best language, I'll show you the no users language---Anders Hejlsberg (TS createor)
JS问题
类型没有上下文,容易包含编译报错陷阱,没有强类型检查
JsDoc文档对代码无约束切重构要重写,结构松散
配套工具较弱
TS优势
2010年创建,12年开源,Anders Hejlsberg 创建(大牛)
四部分:编程语言(JS+特殊类型语法)、类型检查器、编译器(报告问题+输出等价JS)、语言服务(协调编译器工作)
开始
node --version
npm install --location=global typescript (failed with error)
npm i -g typescript (it is ok!)
npm install -g typescript(the same)
tsc --version
具体项目目录
tsc --init
生成tsconfig.json
编译:tsc index.ts
其他:
npx http-server 可以一句话帮你开启一个静态服务器!
npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装!
其他
TS不会和JS冲突
TS编译为JS需要时间,Babel可以更快编译?
2 类型系统
TS基础类型7种
null
undefined
boolean
string
number
bigint
symbol
类型系统:代码中数据类型规则的体系,成为类型系统
TS类型系统:
理解代码所有值的类型
观察初始值,推断可能包含什么类型
观察每个值,在代码中的所有用法
如果值和类型不匹配,报错提示
返回ts代码两种错误类型:
语法错误,写错了,词不达意
类型错误,方法或者值与类型不匹配(可赋值性 assignability+方法属性)
如果没有那些属性,则ts会报错,成为类型形状 type shapes
类型推断不匹配的报错,可以显式说明变量类型,成为类型注释
let body_weight: number = 49;
使用类型注释解决类型推断不符合预期的问题
避免变量没有初始值和类型,因为会被隐式推断为any类型,是风险
let birthplace: string;
birthplace = true; (会报错提示)
无法推断类型的根据赋值自动识别为对应类型,也具备了对应类型的方法
约定
同目录下多个ts文件
包含import或者export语句,则该文件是一个模块 module
不包含上述的,就是普通ts代码,是script
变量名冲突
1.模块内变量名的冲突,根据是否有import及import的变量名(别名)是否和本模块相同,相同则冲突。
2.不同脚本中存在同名变量,会产生冲突,可tsconfig中配置更改冲突规则。
3 联合和字面量?
Unions: 允许两个多多个类型的赋值
let mathematician = Math.random()>0.5 ? "祖冲之" : undefined;
let thinker: string|null;
这种变量因为可能是两种类型,则访问该变量方法,会排除单个类型的方法,保留共有属性方法
narrowing:将值允许的类型减少为不是一个或多个可能的类型?
类型守卫代码<type guard>,指示ts此处(联合类型)的具体类型,就可以访问该类型独有的属性,这个过程叫narrowing.
有几种方式收缩:
给一个确定类型的初始值
对变量的具体值进行判断
对变量值的类型进行判断
字面量 literals
一般是指const固定的类型,固定了变量类型和值,具有对应类型的属性方法
这个是联合类型混合了字面量类型的变量:
let score: number|"不及格"|"及格"|"良好"|"优秀";
虚值
falsy: false 0 -0 0n "" null undefined NaN
真值
不等于虚值的就是真值
严格空值检查
Tsconfig.json中的 strictNullChecks = true ,则null和undefined不能赋值给其他类型
类型别名
type关键字可以给某类型一个别名,如果它很长的话,常用大驼峰命名,可在任意处定义,存在即可。
type RawData = boolean | number | string |null |undefined;
let raw_data_first: RawData = false;
type IDMabe = Id | undefined | null;
type Id = number | string;
4 对象
对象字面量
对象类型匹配对应的属性和属性类型,缺少/增加属性,属性类型不匹配,都会产生类型错误。
变量/属性+?表示可选
let singer: {
name: string,
age?: number,
work_title: string|undefined,
} = {
name: "张纪中",
// age属性可以不存在
work_title: "导演" // work_title 必须存在,可以是undefined。
};
1.使用字面变量赋值时,必须严格满足对象类型要求,不能有额外属性。
2.而将对象赋值给变量,则只要求该对象包含变量类型要求的属性,对象有额外属性不会报错。
type WithFirstName = {first_name: string;};
type WithLastName = {last_name: string;};
const has_both = {
first_name: "Lucas",
last_name: "Clif"
};
let with_first_name: WithFirstName = has_both;
变量被赋值一个对象,只要对象有变量类型的一个属性,则可以赋值
const has_both_error: WithFirstName = {
first_name: "Lucas",
last_name: "Clif" //这里编译器会报错,因为变量被赋值的是字面量,则要求属性完全匹配
};
ps:错误的代码编译为js后,可以正常输出,js的容忍度很高啊
推断的联合对象类型
不是共有属性的,在单个类型中以可选属性表示,属性类型是undefined
let best_song = Math.random()>0.5 ? {
name: "song1",
single: true,
duration: "4:08"
} : {
name:"song2",
single: "是",
release: new Date(2022, 0, 20)
};
显式的联合对象类型,非共有属性,不可直接访问,需要narrowing。
if in判断属性是否存在
或者增加判断属性来访问判断
intersection类型
更像是满足多个类型的类型,而不是满足多个类型的共有部分?
let one_written_art: Artwork & Writing = {
enre: "genre",
pages: 100,
name: "name"
}
type Artwork = {
genre: string;
name: string;
};
type Writing ={
pages: number;
name: string;
};
多个类型interset时,某个属性名相同单类型不同时,这个属性就是never类型
never类型表示不可能出现在代码的类型状态。
5 函数
函数参数
parameter,定义的参数,形式参数,行参
argument,传递的参数,实际参数,实参
形参:type
形参?: type,表示可选参数,可以有多个
形参 = default value,表示默认参数,不用标记类型,自动根据值判断类型
其余参数定义: function sing_all_songs(singer: string, ...songs: string[]) {}
实参直接值,类型与形参类型相同,数量也相同
作为返回类型: ts根据返回值判断返回类型
1.多个return语句(活return结果可能不同)的函数,返回值类型是每个return结果的联合类型
2.显式返回值类型: 用箭头函数,后面是返回值类型 function aaa(aa: string, bb=0): number {}
显式返回可以提升类型检查速度,不必推断,或有必要强制返回值类型时
函数类型:写法和箭头函数类似,知识返回值变为返回值类型,function aaa(v: string) => string {}
void函数返回值类型表示返回nothing,undefined不匹配void类型
never返回值类型表示never return,用于总抛出错误或者在无限循环结构的函数。
函数重载 overload
因为传入参数形式不同而执行不同动作,即函数有多个版本,成为函数重载。
1.先重载签名,多次声明函数,函数名相同,参数形式和返回值不同,不写函数体
2.然后实现重载签名,完成函数体内容,记住要兼容所有重载签名才可以
重载是最后手段,ts编译为js后,重载签名会被清除
6 数组
数组和元组(array和tuple)
type [] or [values],依靠值判断类型
let variance: number[];
Variance = [41, -7, 0, 6.4];
联合数组类型:type array_of_string_or_numbers = (string | number)[];
避免any类型的数组,例如let aa = [];数组类型会不断根据值而变化
let three_demesional : boolean[][][][];
let complex: ((Symbo[] | bigint[])[])[];
TS的默认数组类型认为访问数组元素时一定能访问到,及时超出index,有开关可改变。
扩展运算:合并数组,不同类型将产生联合类型
剩余参数类型:...
const aa = [pp"","rr","ss"];
const bb = [4,5,8,9];
const joined_array = [...aa, ...bb];
function bubble(message: string, ...stuff: string[]){}
let abc: [string, number]; //注意:这个是元组,不是数组,数组是 let abc: (string | number)[];
优先判断为数组类型而非元组
显式注释元组类型: let m_tuple: [bigint, number] = [0n, 0];
在数字字面量后写上常量断言(as const): const readonly_tuple = [0n, 0] as const;
7 接口
做自己的类型,超越内置类型
接口:描述对象的形状,和对象类型的别名很类似。
1.提供易于阅读的错误信息
2.很快的编译速度
3.与类的交互性很好
//类型别名
type Book = {
title: string;
pages: number;
};
//等效的接口
interface IBook {
title: string;
pages: number;
};
let loved_book: IBook = {
title: "Master C#",
pages: 705
}
接口和类型别名区别:
1.接口可以合并(相同名字的接口)以增强功能,在协同第三方代码,如npm包很有用
2.接口可以描述类(class)的结构,类型别名不行
3.接口容易在checker内缓存,有更快的类型检查速度,类型别名本质是粘贴了对象类型的字面量。
4.接口被视为命名的对象,而不是一个未命名的字面量的别名,有更易阅读的错误信息
接口属性类型
attribute?:,表示可选属性
Readonly attribute, 表示只读属性(编译为JS此属性会失效)
接口函数和方法
方法语法:Member(): void;
属性语法:Member: () => void;
staff.member(); // 以上两种定义这里都正确
方法与函都支持可选修饰符,方法与函数大多数情况下是等同的,可交换。
二者区别:
1.方法不可以被readonly修饰
2.接口合并时,对方法和函数的处理不同
3.一些类型操作,对待方法和函数不同
如果你知道函数可能使用this,那用方法语法,否则可以使用属性语法
调用签名
一个值可以像函数一样调用,若函数有特殊的内部属性,可以调用签名来描述
没太看懂必要性?
索引签名
定义接口太多了就可以索引
你想存储键值时,key是位置的,用Map更安全,因为包含了undefined联合类型
key的类型只能是string, number, symbol
接口嵌套
interface Novel {
author: {
name: string;
age?: number;
},
setting: Setting;
}
interface Setting {
place: string;
readonly year: number;
}
let hovel: Novel = {
author: {
name: "jjj"
},
setting: {
place: "England",
year: 1199
}
};
接口扩展(派生)
一个接口可以扩展另外一个接口,派生接口复制基础接口所有成员,还可以添加自己的新成员,让代码复用。
interface Writing {
title: string;
review?:(() => void) | boolean[];
publish?(): symbol;
}
interface Novella extends Writing {
pages: number; //新增属性
review: boolean[]; //覆盖属性,保障类型匹配
pulibsh?: () => bigint; //复制属性,类型不对
}
扩展多个属性
interface Avert extends Revert, Invert {
...
}
合并接口
合并的接口要同名,同一作用域。
一般为了增强功能,比如自定义一个接口来合并内置的全局接口,比如Window,或者外部包的接口
合并接口的属性类型必须一模一样
8 类
TS对待方法和构造器就像对待函数,允许声明参数类型、可选参数、参数默认值、剩余参数和函数返回值。
类的属性必须显式声明,不会从构造器的赋值来反推有哪些属性。
两种语法,让类中成员能像函数一样调用:
1.方法语法,创建的函数叫方法,不同实例访问的方法都是同一个函数。
2.属性语法,创建一个函数赋值给属性,不同实例有自己的属性,属性语法创建的函数是相互独立的。
初始化检查
TS默认开启严格的属性初始化检查:
1.属性类型不包含undefined
2.属性值没有初始值
3.没有在构造器为此属性复制
那TS会抱怨这个属性的不确定性,当然,属性名后面加!, attribute!: string;
就跳过了严格检查,表明第一次使用前一定会赋值。
可选属性在初始化检查中会跳过
只读属性指南在声明初始化或者构造器中初始化,不能在类方法、属性或者实例中初始化
类作为类型
一个对象结构满足类的要求,及时该对象不是类的实例,该对象也能匹配类(就是类的类型)
因为TS结构化检查只关心形状,不关心对象如何声明。
类与接口
类能够实现接口,让类实例遵循接口的类型要求
类实现接口,不改变类的使用,不会复制接口的任何成员到类中??,TS不会跟军接口来推断类成员类型
接口只是起到纯净的安全性检查作用,允许类实现多个接口,不冲突即可
当接口被设计去让类实现时,接口的函数应该使用方法语法定义
不能同时满足两个接口的要求,智能实现其中一个
类的扩展
类能像接口那样扩展,子类拥有所有基类的属性和方法
子类没有写自己的构造器,则会隐式调用积累构造器。
子类如果有自己构造器,则必须通过super()调用基类构造器。
子类必须调用基类构造器,才可以访问this,super。最好把super()写在构造器的顶层语句。(前后为鸡和蛋了。。。)
覆盖方法/属性
方法可覆盖,单参数类型要匹配,返回值类型要匹配,匹配指兼容,非复制
属性同样,可覆盖,保持类型匹配,兼容
抽象类
抽象类是一个基类,不说明实现,值描述结构,其他类扩展抽象类后,可以实例化,抽象类不可实例化。
类名或方法前加 abstact即可
成员可见性
Public 默认的,允许任何人,任何地点访问
Protected, 允许在类本身和其子类访问
Private,允许类本身访问
#,允许在类本身访问??有啥区别?#是JS原版的,其他关键字是TS加的,#是原始私有成员的标志
#不能与其他TS可见性关键字混用,可以和readonly,static混用;readonly与可见性关键字可以混用
注意static静态属性访问需要类名而不是this来访问
实例不能访问protected和private
注意static修饰符,表示该字段/成员只能被类本身(不是在类本身中)访问,例如类名.static变量
顺序:可见性关键字 static readonly