TypeScript入门学习记录

368 阅读8分钟

前言:TypeScript是Js的一个超集,并且支持ES6,是一种面向对象的编程方法,常用的用法主要包括以下几个部分:基础类型,类型批注,类型推断,接口,枚举,修饰符,泛型,命名空间,类,元组等。

TypeScript安装

使用npm安装

npm config set registry https://registry.npm.taobao.org

安装TypeScript

npm install -g typescript

然后就可以用tsc命令来编译ts文件

tsc -v #查看ts版本号
tsc app.ts #编译指定ts文件

编译完成后会在该文件的相同目录下生成app.js文件,然后使用node app.js即可运行该文件

或者安装ts-node插件,然后可以直接使用命令ts-node运行ts文件

ts-node app.ts

TypeScript变量声明

在ts中,变量声明的时候同时需要声明变量的类型

  • 变量类型:
const count :number=111
const myName:String="aaa"
  • 对象类型:
const myObj:{
    name:string,
    age:number
}={
    name:"张三",
    age:18
}
  • 数组类型:
const names:string[]=["aaa","bbb","ccc"]
  • 类类型:
class Person{}
const zhangsan:Person=new Person()
  • 函数类型:
const seeZhangSan:()=>string=()=>{return "lisi"}
  • 联合类型
var val:string|number 
val = 12 
console.log("数字为 "+ val) //数字为 12
val = "Hello" 
console.log("字符串为 " + val) //字符串为 Hello
//如果赋值其他类型就会报错
var val:string|number 
val = true //报错

也可以将联合类型作为函数参数使用

function disp(name:string|string[]) { 
        if(typeof name == "string") { 
                console.log(name) 
        } else { 
                var i; 
                for(i = 0;i<name.length;i++) { 
                console.log(name[i])
                } 
        } 
} 
disp("Runoob") 
console.log("输出数组....") 
disp(["Runoob","Google","Taobao","Facebook"])

联合类型数组

var arr:number[]|string[]; 
var i:number; 
arr = [1,2,4] 
console.log("**数字数组**")  
 
for(i = 0;i<arr.length;i++) { 
   console.log(arr[i]) 
}  
 
arr = ["Runoob","Google","Taobao"] 
console.log("**字符串数组**")  
 
for(i = 0;i<arr.length;i++) { 
   console.log(arr[i]) 
}

函数

参数类型与返回值类型

在ts中,函数可以指定参数类型与返回值类型

//函数参数类型注解
function getTotal1(one:number,two:number){
    return one+two
}
let total1=getTotal1(1,2)
//函数返回值类型注解
function getTotal2(one:number,two:number):number{
    return one+two
}
let total2=getTotal2(1,2)

//函数参数是对象时的注解方式
function add({one,two}:{one:number,two:number}){
    return one+two
}

let total3=add({one:1,two:2})
function getNumber({one}:{one:number}){
    return one
}
let one=getNumber({one:1})

可选参数与默认参数

  • 可选参数 在ts函数中,可以使用?定义可选参数
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
 
let result1 = buildName("Bob");  // 正确
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,参数太多了
let result3 = buildName("Bob", "Adams");  // 正确
  • 默认参数 可以指定参数的默认传入值,如果调用该函数时未传入参数则使用该默认值
function calculate_discount(price:number,rate:number = 0.50) { 
    var discount = price * rate; 
    console.log("计算结果: ",discount); 
} 
calculate_discount(1000) 
calculate_discount(1000,0.30)
  • 剩余参数 当我们不知道要向函数传入多少个参数,这个时候我们就可以使用剩余参数来定义,剩余参数允许我们将一个不缺性数量的参数作为数组传入
function addNumbers(...nums:number[]) {  
    var i;   
    var sum:number = 0; 
    for(i = 0;i<nums.length;i++) { 
       sum = sum + nums[i]; 
    } 
    console.log("和为:",sum) 
 } 
 addNumbers(1,2,3) 
 addNumbers(10,10,10,10,10)

数组

  • 数组解构
var arr:number[] = [12,13] 
var[x,y] = arr // 将数组的两个元素赋值给变量 x 和 y
console.log(x) //12
console.log(y) //13
  • 数组作为参数传递给函数
var sites:string[] = new Array("Google","Runoob","Taobao","Facebook") 
 
function disp(arr_sites:string[]) {
        for(var i = 0;i<arr_sites.length;i++) { 
                console.log(arr_sites[i]) 
        }  
}  
disp(sites);
  • 作为函数的返回值
function disp():string[] { 
        return new Array("Google", "Runoob", "Taobao", "Facebook");
} 
 
var sites:string[] = disp() 
for(var i in sites) { 
        console.log(sites[i]) 
}

枚举类型enum

  1. 基础用法:
enum Color {Red, Green, Blue}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
console.log('enum',c,d,e) //0,1,2

2.手动设置初始值 第一位未设置的默认0,后面递增.遇到有初始值的,后面的按照初始值+1.

enum Color {Red, Green=2, Blue}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
console.log('enum',c,d,e) //0,2,3
enum Color {Red, Green=2, Blue,Yellow=7 ,Dark}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
let f: Color = Color.Yellow;
let g: Color = Color.Dark;
console.log('enum',c,d,e,f,g) //0 2 3 7 8

3.属性获取

在赋予初始值的时候是以键值对的形式给的,那怎么拿到'键'呢?

enum Color {Red, Green=2, Blue,Yellow=7 ,Dark}
let c1: string = Color[0];
let c: Color = Color.Red;
let d1: string = Color[1];
let d: Color = Color.Green;
let e1: string = Color[2];
let e: Color = Color.Blue;
let f1: string = Color[3];
let f: Color = Color.Yellow;
let g1: string = Color[4];
let g: Color = Color.Dark;
console.log('enum',c1,c,d1,d,e1,e,f1,f,g1,g) 
//Red 0 undefined 2 Green 3 Blue 7 undefined 8

这里会出现undefined的原因是因为[0]这里面的0代表的是键所对应的值,因为没有一个键是1或者4(键值只存在0 2 3 7 8),所以1和4键对应的值是undefined 4. 设置初始值为字符串 假如设置的字符串不是最后一位,那后面的属性将无法设置默认值.我们之前说过要递增+1,如果前一个是字符串,ts将无法处理初始化.

enum Color {Red, Green=2, Blue,Yellow='b' ,Dark='b'}
let g: Color = Color.Dark;
let test: string = Color['b'];
console.log('enum',g,test) //b undefined

同时我们,发现,并不能用字符串值拿到键位值,那么怎么拿到呢?经过查询资料得知,字符串赋值之后不进行反向映射.故拿不到对应键位值.

总结一下:

  1. 枚举成员可以是纯数值,纯字符串,数值字符串混合,三种情况.不能使用计算值,
  2. 涉及字符串混合的限制条件比较多,所以尽量避免字符串的混合.
  3. 添加的索引为赋予的值,没有就是默认值.
  4. 字符串并没有添加索引,所以无法根据值获得键.

TypeScript 接口 interface

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法类调用。interface本质就是一个类的说明书,是约束条件

interface People {
  name: string;
  age: number;
  height: number;
  //?表示该属性可存在可不存在
  weight?: number;
  //可以添加任意属性,该属性名称为字符串类型,同时该属性的内容为任意
  [propname: string]: any;
  //函数返回类型为string
  say(): string;
}

类似的一个用法type

//type alias类型别名
type Lady={name:string,age:number}
const people2:Lady[]=[
    {name:'zhangsan',age:11},
    {name:'lisi',age:22},
]
class Madam{
    name:string;
    age:number
}
const people3:Madam[]=[ 
    {name:'zhangsan',age:11},
    {name:'lisi',age:22},
]

这里存在一个和接口类似的定义type

interface和type的异同

  • interface能够声明合并,type不行
interface User {
  name: string
  age: number
}
interface User {
  sex: string
}
/*
User 接口为 {
  name: string
  age: number
  sex: string
}
*/
  • type可以声明基本类型别名,联合类型,元组等类型,interface不行
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

接口继承及使用

//接口继承
interface NewPeople extends People {
  done(): string;
}

const boy = {
  name: "zhansan",
  age: 18,
  height: 160,
  weight: 100,
  sex: "女",
  phone: 111111111,
  say() {
    return "i am zhangsan";
  },
  done() {
    return "i can done";
  },
};

const screenResume = (boy: People) => {
  boy.age < 24 && boy.height > 170 && console.log(boy.name + "进入面试");
  boy.age >= 24 || (boy.height < 170 && console.log(boy.name + "已被淘汰"));
};

const getResum = (boy: NewPeople) => {
  console.log(boy.name + "年龄是" + boy.age);
  console.log(boy.name + "身高是" + boy.height);
  boy.weight && console.log(boy.name + "体重是" + boy.weight);
  boy.sex && console.log(boy.name + "性别是" + boy.sex);
  console.log(boy.say())
  console.log(boy.done())
};

screenResume(boy);
getResum(boy);

类的概念和使用

类的使用(构造一个类并实例化,然后调用其参数和方法)
class Car { 
   // 字段
   engine:string; 
   // 构造函数
   constructor(engine:string) { 
      this.engine = engine 
   }  
   // 方法
   disp():void { 
      console.log("函数中显示发动机型号  :   "+this.engine) 
   } 
} 
// 创建一个对象
var obj = new Car("XXSY1")
// 访问字段
console.log("读取发动机型号 :  "+obj.engine)  
// 访问方法
obj.disp()
类的继承
class People{
    content="hi"
    sayHello(){
        return this.content
    }
}
class Man1 extends People{
    sayHello(){
        //重写父类的方法时,可以使用super关键字调用父类的方法或属性
        return super.sayHello()+" 你好"
    }
    sayLove(){
        return "i love you"
    }
}
const goodess=new Man1()
console.log(goodess.sayHello())
console.log(goodess.sayLove())

需要注意的是子类只能继承一个父类,TypeScript 不支持继承多个类,但支持多重继承

class Root { 
   str:string; 
} 
class Child extends Root {} 
class Leaf extends Child {} // 多重继承,继承了 Child 和 Root 类
var obj = new Leaf(); 
obj.str ="hello" 
console.log(obj.str)

类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。 其中 super 关键字是对父类的直接引用,该关键字可以引用父类的属性和方法。

class PrinterClass { 
   doPrint():void {
      console.log("父类的 doPrint() 方法。") 
   } 
} 
class StringPrinter extends PrinterClass { 
   doPrint():void { 
      super.doPrint() // 调用父类的函数
      console.log("子类的 doPrint()方法。")
   } 
}
类的访问类型
  • public 公共属性类型,可以在任何地方被访问
  • private 受保护,可以被其自身以及其子类和父类访问,只能在类的内容使用,继承也不可以
  • protected 私有,只能被其定义所在的类访问,只能在类的内部使用,继承中也可以使用
class Person1{
    private name:string;
    public sayHello(){
        //使用私有属性不报错
        console.log(this.name+'say hello')
    }
}
const person=new Person1()
//使用私有属性报错
person.name='ts'
console.log(person.name)
类的构造函数
class Person2{
    public name:string;
    constructor(name:string){
        this.name=name;
    }
}
class NewPerson2 extends Person2{
    constructor(public age:number){
        super("newts")
    }
}

const person2=new Person2('ts')
console.log(person2.name)
const newperson=new NewPerson2(18)
console.log(newperson.name)
console.log(newperson.age)

class People4{
    constructor(private _age:number){}
    get age(){
        return this._age
    }
    set age(newage:number){
        this._age=newage+10
    }
}
const onepeople=new People4(28)
console.log(onepeople.age)
onepeople.age=22
console.log(onepeople.age)
类的静态方法

static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。

//静态方法
class People5{
    static sayLove(){
        return 'I love you'
    }
}
const onepeople2=new People5()
console.log(People5.sayLove())
类的只读属性
//只读属性
class People6{
    public readonly _name:string
    constructor(name:string){
        this._name=name
    }
}
const person6=new People6('ts')
console.log(person6._name)
类和接口

类可以实现接口,使用关键字 implements,并将其字段作为类的属性使用

class Man implements People {
  //implement关键字表示该类Man受到接口People的约束
  name = "zhangsan";
  age = 11;
  height = 180;
  say() {
    return "i can say";
  }
}

泛型

泛型的基本定义

在定义函数,接口,类的时候,不预先指定具体的类型,而在使用的时候在去指定类型的一种特征 泛型的使用情景:

  • 当你的函数、接口或类将处理多种数据类型时;
  • 当函数、接口或类在多个地方使用该数据类型时。
//函数泛型格式如下   函数方法名<T>(参数):返回值
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']

其实泛型也可以理解为一种特殊的参数。例如函数的泛型的作用域就是整个函数作用域 函数也支持使用多个泛型

function identity <T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}

console.log(identity<Number, string>(68, "Semlinker"));

泛型接口

相比之前定义的 identity 函数,新的 identity 函数增加了一个类型变量 U,但该函数的返回类型我们仍然使用 T。如果我们想要返回两种类型的对象该怎么办呢?针对这个问题,我们有多种方案,其中一种就是使用元组,即为元组设置通用的类型

function identity <T, U>(value: T, message: U) : [T, U] {
  return [value, message];
}

更好的解决方案是使用泛型接口

interface Identities<V, M> {
  value: V,
  message: M
}
function identity<T, U> (value: T, message: U): Identities<T, U> {
  console.log(value + ": " + typeof (value));
  console.log(message + ": " + typeof (message));
  let identities: Identities<T, U> = {
    value,
    message
  };
  return identities;
}
console.log(identity(68, "Semlinker"));
//输出
//68: number
//Semlinker: string
//{value: 68, message: "Semlinker"}

在上述的Identities接口中,我们引入了变量类型V和M,来进一步说明有效的字母都可以用于表示类型变量,之后,之后就可以将Identities接口作为identity函数的返回类型

泛型类

在类中使用泛型,只需要在类名后面,使用<T,...>的语法定义任意多个变量类型

interface GenericInterface<U> {
  value: U
  getIdentity: () => U
}

class IdentityClass<T> implements GenericInterface<T> {
  value: T
  constructor(value: T) {
    this.value = value
  }
  getIdentity(): T {
    return this.value
  }
}
const myNumberClass = new IdentityClass<Number>(68);
console.log(myNumberClass.getIdentity()); // 68

const myStringClass = new IdentityClass<string>("Semlinker!");
console.log(myStringClass.getIdentity()); // Semlinker!

这里以实例化myNumberClass为例分析其调用过程

  • 在实例化IdentityClass对象时,传入Number类型和构造函数参数值68
  • 之后在IdentityClass类中,类型变量T变成了Number类型
  • IdentityClass实现了GenericInterface<T>接口,此时T表示Number类型,因此等价于该类实现了GenericInterface<Number>接口
  • 而对于 GenericInterface<U> 接口来说,类型变量 U 也变成了 Number。这里我有意使用不同的变量名,以表明类型值沿链向上传播,且与变量名无关。

泛型约束

  1. 确保属性存在 举个例子,在处理字符串或者函数时,我们回假设length属性是可用的,所以当我们直接输出arg.length属性的时候,是有可能报错的 所以为了保证编译器知道T一定含有length属性,我们需要让类型变量<T>extend一个含有我们所需要属性的接口
interface Length {
  length: number;
}

function identity<T extends Length>(arg: T): T {
  console.log(arg.length); // 可以获取length属性
  return arg;
}

<T extends Length>这段代码就告诉编译器,我们支持已经实现Length接口的任何类型,之后,当我们使用不含有length属性的对象作为参数调用identity函数时,Ts编译器就会提示想滚的错误信息

identity(68); // Error
// Argument of type '68' is not assignable to parameter of type 'Length'.(2345)

此外,我们还可以使用 , 号来分隔多种约束类型,比如:<T extends Length, Type2, Type3>。而对于上述的 length 属性问题来说,如果我们显式地将变量设置为数组类型,也可以解决该问题,具体方式如下:

function identity<T>(arg: T[]): T[] {
   console.log(arg.length);  
   return arg; 
}

// or
function identity<T>(arg: Array<T>): Array<T> {      
  console.log(arg.length);
  return arg; 
}
  1. 检查对象上的键是否存在 在具体使用之前要先了解一些keyof操作符,keyof在TypeScript2.1版本引入,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

通过 keyof 操作符,我们就可以获取指定类型的所有键,之后我们就可以结合前面介绍的 extends 约束,即限制输入的属性名包含在 keyof 返回的联合类型中。具体的使用方式如下:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

在以上的 getProperty 函数中,我们通过 K extends keyof T 确保参数 key 一定是对象中含有的键,这样就不会发生运行时错误。这是一个类型安全的解决方案,与简单调用 let value = obj[key] 不同。

enum Difficulty {
  Easy,
  Intermediate,
  Hard
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let tsInfo = {
   name: "Typescript",
   supersetOf: "Javascript",
   difficulty: Difficulty.Intermediate
}
 
let difficulty: Difficulty = 
  getProperty(tsInfo, 'difficulty'); // OK

let supersetOf: string = 
  getProperty(tsInfo, 'superset_of'); // Error
//报错信息
Argument of type '"superset_of"' is not assignable to parameter of type 
'"difficulty" | "name" | "supersetOf"'.(2345)

ts配置文件tsconfig.json的使用

使用步骤:

  1. tsc -init生成tsconfig.json(在终端中私用tsc命令,则会自动对该文件夹下的所有ts文件执行tsconcig.json)
  2. 配置tsconfig中的配置项
  • 使用"include"配置项指定要编译的ts文件
  • exclude指定不编译的文件
  • include包含的文件可以使用exclude排除,被files白喊的文件不会被exclude排除
  • include可以是目录以及文件,而files只能是文件
  • files指定一个包含相对或绝对文件路径的列表,include和exclude属性指定一个文件glob匹配模式列表