TypeScript的基本用法 | 青训营笔记

158 阅读11分钟

TypeScript用法 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的第1天。

经过浅浅地学习后,我能够体会到TS给开发带来的稳定性,虽然在写代码时需要考虑的东西更多,但是能规范自己的代码,也增强了可读性和可维护性,在团队协作中能获得更好的开发效率。

今天主要给大家分享一下我在学习《TypeScript入门》课程后总结的部分TS用法。

基本使用

基本类型

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表示stringconst 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中是否含有某属性。

image.png

注意上图断言是因为Human中可能不含有swim或fly,因此不能直接调用。

instanceof 缩小

在判断一个实例是否是某个类的实例时用到。

image.png

分配缩小

定义一个变量时如果是以条件运算符定义,则可以看作是联合类型。

image.png

类型谓词

用于构建判断函数,判断变量类型。

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);

构造签名

用于定义构造函数的类型

image.png

泛型函数

function func <Type>(param:Type):Type{
    // 阔以用泛型定义函数类型、参数类型以及返回值类型
}

注意,如果返回值是泛型,则必须也返回泛型值,不能返回其他类型。

可选参数

function f(n?:number){
    
}

重载函数

image.png

注意重载签名和实现签名的参数类型和返回值类型需要兼容。

参数

参数解构

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中,可以用getset关键字来定义读写属性的方法,例如:

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'
}

类型也可以:

image.png

泛型变量

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所指的属性,则会报错
}