TypeScript用法 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第1天。
经过浅浅地学习后,我能够体会到TS给开发带来的稳定性,虽然在写代码时需要考虑的东西更多,但是能规范自己的代码,也增强了可读性和可维护性,在团队协作中能获得更好的开发效率。
今天主要给大家分享一下我在学习《TypeScript入门》课程后总结的部分TS用法。
基本使用
基本类型
-
字面量:
let a :10; // 这表示 a只能是10,无法改变 // 也可以用|来列举多个值 let a:10|20; // 表示a可以是10也可以是20 -
any
//隐式 let a; //显式 let a:any;一般不建议使用;
当赋值给别的已经限制类型的变量后,该变量也会变成any
-
unknown,未知类型
实际上是安全的any,与any的区别在于赋值给别的变量后,不会污染别的变量,且会报错;
-
never 没有值
一般用于报错函数:throw...
-
tuple
元组:固定长度数组
let h:[type,type] //几个元素就几个 -
enum
可以定义几个值,用于对一些值数量固定的变量定义,可以增强语义化
enum Gender{ Male = 0, Female = 1 } let person:{name:string,gender:Gender}; person = { name:'Curry', gender:Gender.Male }
类型声明
变量
let x:type;
let x = ... //以第一次赋值为类型
联合类型
let a:boolean | string;
//表示a可以是布尔也可以是字符串
两个interface也可以联合:
interface Circle = {
type:'circle',
radius:number
}
interface Square = {
type:'square',
sidelength:number
}
interface Shape = Circle | Square;
function getArea(shape:Shape){
if(shape.type === 'circle'){}
}
函数
function func(a:number,b:number){}
函数中定义好参数后,如果传入的参数类型不对,或者数量不对都会报错。
可以定义函数返回值类型
let d:(a:number,b:number)=>number; //声明
let d:(a:number,b:number):number{}
规定返回值类型
function func(...):number{}
各种限制
对象
一般声明时所写的属性,在修改时需要所有属性一起写,不完整会报错。
可以用&定义:
let m:{name:string} & {age:number}
//表示m既有两个属性,且两个属性都有各自类型
可以用一个type类型的对象来描述一个对象的类型:
type myType = {
name: string,
age: number
}
const obj: myType = {
name: 'Curry',
age: 35
}
数组
let e:string[] //字符串数组
let e:Array<string> //第二种方式
类型别名
type username = string;
// 则表示username表示string
const getUserName = ():username => { // 提前定义好函数的返回值类型,增加可读性
return 'asd'
}
// 当两个type都以对象形式定义,则可以用&组合起来
type stu = {
id:number
}
type user = stu & {
age:number
}
文字类型
const,用于规定输入的字符串的字面量,如:
function print (s:string,direction: 'left' | 'right'){
}
print('hello','left');
print('hello','right'); // 以文字类型的方式规定之后只能传left或right
function handleRequest(url:string,method: 'GET' | 'POST'||'GUESS'){}
const req = {
url: 'https://test.cn',
method: 'GET'
}
handleRequest(req.url,req.method);
// 注意,这里会报错,因为ts识别req.method是字符串,而非'GET' | 'POST'||'GUESS'
// 解法:在引用的对象后面加上文字类型断言
// 相当于把字符串的值识别为其字面量,即 method:string =>> method:'GET'
const req = {
// ...
} as const
类型断言
用于声明元素的类型,例如
let ele = document.getElementById('ele') as HTMLElement;
// 相当于
let ele = <HTMLElement>document.getElementById('ele')
类型保护(缩小)
即区分类型做出对应类型的操作。
真值缩小
使用条件、&& 、||、if 和 ! 进行判断的类型区分
const func = (a:string|number|null) => {
if(a{
// ...
}
}
typeof
用于判断参数的类型,但只能判断number、string、boolean和symbol;
if(typeof v === "typename") {}
if(typeof v !== "typename") {}
等值缩小
使用===或 !==来判断类型,例如:
const func = (a:string|number|null) => {
if(a !== null){
// ...
}
}
in 类型缩小
in用于查找对象、type或interface中是否含有某属性。
注意上图断言是因为Human中可能不含有swim或fly,因此不能直接调用。
instanceof 缩小
在判断一个实例是否是某个类的实例时用到。
分配缩小
定义一个变量时如果是以条件运算符定义,则可以看作是联合类型。
类型谓词
用于构建判断函数,判断变量类型。
function isCar(vehicle: any): vehicle is Car {
return (vehicle as Car).turnSteeringWheel !== undefined;
}
// 此函数可以传入任意类型的数据,通过判断有无turnSteeringWheel属性来确定是否是Car类型
// 此函数的返回值是boolean,但是这个Boolean是回答vehicle is Car的,也就是如果是Car类型才返回true
由此可以定义一个通用类型保护函数:
function isOfType<T>(
varToBeChecked: any,
propertyToCheckFor: keyof T
): varToBeChecked is T {
return (varToBeChecked as T)[propertyToCheckFor] !== undefined;
}
// 用法:
// isCar(anotherCar) -> isOfType<Car>(vehicle, 'turnSteeringWheel')
if (isOfType<Car>(vehicle, 'turnSteeringWheel')) { // 尖括号内传类型,后面带参数和要判断的属性
anotherCar.turnSteeringWheel('left');
console.log("这是一辆车");
} else {
console.log("这不是一辆车");
}
never 穷尽性检查
interface Foo {
type: 'foo'
}
interface Bar {
type: 'bar'
}
type All = Foo | Bar;
function handleValue(val: All) {
switch (val.type) {
case 'foo':
// 这里 val 被收窄为 Foo
break
case 'bar':
// val 在这里是 Bar
break
default:
// val 在这里是 never , 当All类型中所有情况都有case处理时则能通过编译,否则会报错(用于检查)
const exhaustiveCheck: never = val
break
}
}
索引签名
可以给数组或对象的索引定义类型及对应元素的值的类型:
interface Some {
[index: string]: number // index: string指索引为字符串,number指元素值为number类型
length:number
name: string // 注意因为上面定义了number类型,所以这里写string会报错
}
交叉类型
类似类型扩展,可以把多个类型组合起来
interface Colorful {
color:string
}
interface Circle{
radius: number
}
type ColorCircle = Colorful & Circle // 注意是type
编译选项
监视
单文件监视
tsc xxx.ts -w
多文件监视
需要先有ts的配置文件:
// tsconfig.json
{}
只要目录中有这个文件,以下语句都可以对全部ts文件起作用:
tsc //编译所有ts
tsc -w //监视所有ts
tsconfig.json
{
//表示哪些ts文件需要被编译
//*表示任意文件 **表示任意目录
"include": ["./src/**/*"],
//不需要被编译的目录 ,可以不设置,有默认值
//"exclude": [],
//定义被继承的配置文件,可以继承配置
//"extends": "",
//指定被编译文件的列表,当需要编译的文件较少时才需要
//"files": [],
//编译器选项,指定编译的方式
"compilerOptions": {
//指定编译后的ES版本,ES5,ES6,ESNext...
//'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext'.
"target": "es2015",
//引入模块的方式
//'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'.
"module": "es2015",
// lib用于指定项目中要使用的库
// 一般不需要改,在浏览器中有默认值
//
//"lib": []
//指定编译后文件所在目录
"outDir": "./dist",
//将所有编译的文件合并为一个文件
//"outFile": "./dist/app.js"
//是否编译js文件
"allowJs": false,
//是否检查js文件语法
"checkJs": false,
//是否编译后移除注释
"removeComments": false,
//是否生成编译后的文件,当只需要检查语法时使用
"noEmit": false,
//当有错误时不生产编译后的文件
"noEmitOnError": false,
//严格检查地总开关,只要开了,则其他都开
"strict":true,
//设置编译后的文件是否使用严格模式,当有export引入模块时默认打开严格模式
"alwaysStrict": false,
//是否允许隐式any类型(未设置类型时可能被当作any,不安全)
"noImplicitAny": true,
//不允许不明确类型的this
"noImplicitThis": true,
//严格地检查变量是否为空,使用if可以避免报错
"strictNullChecks": true,
}
}
接口
interface xxx {}用于定义一个类结构,并说明这个类应该包含那些属性和方法,同时接口也可以当成类型声明来使用。
type mytype = {
name:string,
age: number
}
//类型声明
interface myInterface{
name:string, // 分隔符阔以是分号
age?: number // 在冒号之前写问号表示可以传也可以不传
}
const obj:myInterface = {
name:'das',
age:123
}
接口中的所有属性都不能有实际值,最多只能定义对应属性或值的类型,其中定义的方法都是抽象方法,不能有实际内容。
接口继承
// 不同个接口的继承
interface if1{
name:string
}
interface if2 extends if1{
// extends之后相当于这里多一行 name:string
age:number
}
// 同个接口的字段补充
interface if1{
name: string
}
interface if1{
age:number
}
//相当于
interface if1{
name:string,
age:number
}
类实现接口
定义类时可以使用接口来规范,称为实现接口:
interface myInterface{
name:string,
age: number
}
class myclass implements myInterface{
//implements 表示用后面加的接口来限制类
name: string;
age: number; //必须先写这两行(即接口中的内容)
constructor(){ //且必须在构造器中初始化
this.name = 'name',
this.age = 11
}
//一般可简化为:
constructor(private name:string,private age:number){}
}
与类型别名type的区别
接口可以重复声明,多出来的内容会合并,即添加字段,而type不行:
只读属性
使用readonly关键字
interface SomeType{
readonly prop: string
}
接口合并
多个同名接口会自动合并内容;type则不会
函数
签名
调用签名
// 为函数定制一个type,其中多一个属性用于描述函数
type Descript = {
des:string, // 签名,类型为字符串
(some:number): boolean // 函数类型
}
const doSome = (fn:Descript) => {
console.log(fn.des + fn(6));
}
const fn1 = (n:number) =>{
console.log(n);
return true
}
fn1.des = 'fn1'; // 需要手动加
doSome(fn1);
构造签名
用于定义构造函数的类型
泛型函数
function func <Type>(param:Type):Type{
// 阔以用泛型定义函数类型、参数类型以及返回值类型
}
注意,如果返回值是泛型,则必须也返回泛型值,不能返回其他类型。
可选参数
function f(n?:number){
}
重载函数
注意重载签名和实现签名的参数类型和返回值类型需要兼容。
参数
参数解构
function sum({a, b}: {a:number, b:number}){
return a+b;
}
sum({a:10,b:20})
封装类属性
有三种:public、private 和 protected;
私有属性private xxx只能在类中修改和访问,而公有属性public则可以从实例中修改(默认是public),protected则只能在当前类及其子类读写。
class Person {
constructor(private name:string,private age:number){
this.name = name,
this.age = age
}
getName(){
return this.name //如果要从外部读取到,则需要写方法来返回
}
setAge(value:number){
this.age = value
//修改同样也要写方法
// 由于是在函数中修改,所以可以加判断,比如筛选负数
}
}
const me = new Person('curry',11)
console.log(me.getName());
在ts中,可以用get或set关键字来定义读写属性的方法,例如:
class Person {
private _name:string; 要注意的是,这时最好在属性名前面加上 _ 否则会重名
private _age: number;
get name(){
return this.name
}
set age(value:number){
this.age = value
}
}
const me = new Person('curry',11)
console.log(me.name); //这样就可以通过习惯的方式访问属性
泛型
在定义函数或类时,如果遇到不确定类型则可以使用泛型。
泛型就是调用时才确定类型的函数或类。
为什么不用any? any会使得ts不检查对应的语句。
例如:
function fn<K>(a:K):K{
//这句的意思是:定义一个泛型K,且a的类型是K,整个方法的返回值类型也是K
return a
}
// 不指定泛型
let res1 = fn(a:10) //调用时才确定类型
// 指定泛型
let res2 = fn<string>(a:'hello');
泛型的限制条件
// 一个接口
interface Inter {
length:number
}
// 泛型可以继承一个接口或者类,用于限制方法或类必须有哪个属性
function fn3<T extends Inter>(a:T):number{
//这里继承了Inter接口,使用时必须传入含有length属性的对象
return a.length;
}
// 实例:
const longest = <Type extends {length:number}>(a:Type,b:Type) => {
if(a.length > b.length) {
return a;
}else {
return b;
}
}
多个泛型
可以同时定义多个泛型,但要注意返回值只能有一个,返回值类型也只能选一个
function fn2<T,K>(a:T,b:K):T{
console.log(b);
return a;
}
fn2<string,number>('curry',18)
实例:采用泛型实现一个数字字符数组转数字数组的函数:
const map = <Input,Output>(arr:Input[],fn:(n:Input)=>Output): Output[] => {
return arr.map(fn);
}
const fn = (n:string):number => {
return parseInt(n,10);
}
let arr = ['1','2','3'];
let arr2 = map(arr,fn);
泛型对象类型
可以用泛型动态定义接口类型:
interface Box<Type>{
content: Type
}
// 使用的时候需要自行加入Type属性:
let box: Box<string> = {
content:'boxbox'
}
类型也可以:
泛型变量
function loggingIdentity <T>(arg: T[]): T[] {
// 注意如果要有length属性必须是数组,即T[]
console.log(arg.length);
return arg;
}
// 上面写法等同于: 也就是说,T[] 和 Array<T>是一样的
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length);
return arg;
}
泛型类型
直接定义泛型的类型,可以把泛型变量变为一种类型,例如泛型数组
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
也可以手动加上类型,把类型作为参数传入接口:
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型约束
使用泛型时,如果不做类型区分,可能会报错,比如对数字调用length方法。
因此,可以定义一个接口来描述约束条件,并使用继承实现约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
// 这样就只能传有length属性的数据
loggingIdentity({length: 10, value: 3});
在泛型函数中使用类型参数
function getProperty(obj: T,key: K){
return obj[key] // 类型参数K受T的约束,如果obj中不含key所指的属性,则会报错
}