一篇博客带你入门TypeScript

2020-11-07  本文已影响0人  咧咧0622

使用TypeScript就是为了规范开发环境,但是在真正的运行环境里,TypeScript实际上并不起任何约束作用。

一、安装

1.安装TypeScript

npm install typescript -g

安装完成后控制台输入tsc命令检查是否安装成功,如果出来一大堆英文,就代表你安装好了

image

现在我们可以在自己的电脑上进行typescript的编写了,但是如果我们想要运行一个typescript文件,我们需要先使用tsc命令将.ts文件编译成.js文件,然后才能运行其中的代码。

tsc 文件名.ts
node 文件名.js

这样对我们开发时非常低效的,不过我们可以使用ts-node直接执行typescript文件,调过中间的js转换过程。

2.安装ts-node

npm install -g ts-node

此后我们就可以使用ts-node命令直接执行ts文件了

ts-node 文件名.ts

二、类型注释

我们都知道,JavaScript是一种弱类型语言,弱类型语言对于我们规范开发过程是不利的,类型注释就是TypeScript提出的一种强化语言类型的方案,因此,TypeScript也是一种强类型语言。
比如我们定义了一个变量age是number类型的,那么我们就不能给它附一个其他类型的值。

let age: number;
age = 123;

如上面的例子所示,typescript中对类型的注释就是使用 ":" 关键字,: + 数据类型 即可完成声明

数据类型 关键词
String string
Number number
Boolean boolean
Void void
Any any
Undefined undefined
Null null

1.数组的类型注释

数组中类型统一

const arr: number[] = [1, 2, 3];

数组中类型不统一

const arr: (number | string)[] = [1, "string", 2];

对象数组

const student: { name: string, age: number }[] = [
  { name: "小白", age: 21 },
  { name: "小黑", age: 18 },
];

2.元组的使用

数组的不足

当我们在使用数组处理一个业务时,如果数组中元素的顺序发生了改变,那么我们的业务逻辑就会出现错误,但是数组的类型注释却不会报错,此时我们就要使用元组的类型注释(类型约束)。

// 如果数组中的元素顺序发生变化,数组的类型注释不报错,存在开发隐患
const beauty1: (string | number)[] = ["A", "student", 18]
const beauty2: (string | number)[] = ["A", 18, "student"]

// 使用元组中的类型约束可以解决此隐患
const beauties1: [string, string, number] = ["A", "student", 18]
const beauties2: [string, string, number] = ["A", 18, "student"]    //报错!
image.png

三、Interface的使用

1.接口的基础应用

对于接口的简单应用,我们直接上例子吧,比如我们以学生举一个例子。

// 创建接口
interface Student {
    name: string;
    age: number;
    exam?: number;  //非必须属性在“:”前加上“?”即可
    [propname: string]: any;    //此接口允许添加新的属性
}
// 接口应用实例
const getStudent = (student: Student) => {
    console.log("名字:" + student.name)
    console.log("年龄:" + student.age)
    // 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
    student.exam ? console.log("成绩" + student.exam) : console.log("此人无成绩")
}
// 数据
const xiaobai = {name: "小白", age: 21, exam: 90}
const xiaohei = {name: "小黑", age: 18}
// 以传值的形式调用方法
getStudent(xiaobai)
getStudent(xiaohei)
image.png

2.接口中的方法

基于上面的例子,如果我们想给Student接口中存一个方法work(),那么我们直接在接口中声明方法并约束其返回值类型即可。

interface Student {
    name: string;
    age: number;
    exam?: number;  //非必须属性在“:”前加上“?”即可
    [propname: string]: any;    //此接口允许添加新的属性
    work(): string;     //声明方法的返回值类型为string
}

在接口中声明完方法,接下来我们在实际对象中完善该方法,以便后面的使用。

const xiaobai = {
    name: "小白", age: 21, exam: 90,
    work() {
        return "学习就是我的工作"
    }
}
const getStudent = (student: Student) => {
    console.log("名字:" + student.name)
    console.log("年龄:" + student.age)
    // 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
    student.exam ? console.log("成绩" + student.exam) : console.log("此人无成绩")
    console.log(student.work())
}

3.接口中类对接口的实现

我们在实现类的时候记得要把class写的全一点,否则会报错

class xinsheng implements Student {
    name = "小白";
    age = 21;
    work() {
        return "学习就是我的工作"
    }
}

4.接口之间的继承

Student接口可以作为一个基础,如果我们需要一个新的基于Student接口的方法,比如班长Monitor接口,其中Monitor中有一个特殊的方法收作业shouzuoye,那么我们就可以这么实现:

interface Monitor extends Student {
    shouzuoye(): string;
}

具体实例:

interface Student {
    name: string;
    age: number;
    exam?: number;  //非必须属性在“:”前加上“?”即可
    [propname: string]: any;    //此接口允许添加新的属性
    work(): string;     //声明方法的返回值类型为string
}
// 接口的继承
interface Monitor extends Student {
    shouzuoye(): string;
}
// 接口应用实例
const monitor = (teacher: Monitor) => {
    console.log("名字:" + teacher.name)
    console.log("年龄:" + teacher.age)
    // 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
    teacher.exam ? console.log("成绩" + teacher.exam) : console.log("此人无成绩")
    console.log(teacher.work())
    console.log(teacher.shouzuoye())
}
const xiaohei = {
    name: "小黑", age: 18,
    work() {
        return "学习就是我的工作"
    },
    shouzuoye(){
        return "大家把作业交给我"
    }
}
// 以传值的形式调用方法
monitor(xiaohei)
image.png

四、类的使用

typescript中类的概念和java以及es6中的基本一样,没什么区别,上例子。

1.类的定义及使用

class Person {
    name = "小黑";
    age = 18;
    say() {
        return "我叫" + this.name
    }
}
const p = new Person();
console.log(p.say());

2.类的继承

子类继承父类的所有属性及方法,并且可以直接调用。

class Student extends Person{
    work(){
        return "淦!我是学生,我得学习。"
    }
}
const S = new Student();
console.log(S.say())
console.log(S.work())

image.png

3.类的重写以及super关键字的使用

子类可以重写父类中的方法,写法就是把父类中的方法重新写一遍,其中如果我们需要调用父类中的属性,那么我们可以通过super关键字进行调用。

class Student extends Person{
    work(){
        return "淦!我是学生,我得学习。"
    }
    // 重写父类中的say()方法
    say(){
        return super.say() + "很高兴认识你!"
    }
}
const S = new Student();
console.log(S.say())
console.log(S.work())
image.png

4.类的访问类型以及只读属性

类的访问类型有publicprivateprotected

public

typescript默认的访问类型,如果你在写代码的时候没有声明访问类型,那么typescript会默认为public类型。
public访问类型在类的内部外部都能使用,例如我们在类的内部声明了一个变量,我们在类的外部就可以直接调用该变量,也可以修改此变量

class Person{
    public name:string;
    public say(){
        console.log(this.name)
    }
}
const p = new Person();
p.say() //第一次
p.name = "小白"
p.say() //第二次

两次的输出结果表明类的内部变量被外部操作修改了。


image.png

private

私有类型,和上面的public正好相反,可以这么理解,只要出了class的大括号,没有任何操作能直接调用他(注意这里是直接调用,后面我们会有操作私有类型的方法),这里就不给大家演示例子了。

protected

保护类型,此类型和private差不多,都是出了大括号就不能直接操作,但是protected允许在继承时被操作

class Person{
    protected name:string;
    public say(){
        console.log(this.name)
    }
}
class Student extends Person{
    name = "小黑";
    sayHello(){
        console.log(this.name)
    }
}
const p = new Student();
p.say()
p.sayHello()
image.png

只读属性readonly

在类中声明变量时可以定义变量的类型,其中有一个属性叫readonly只读属性,加上此属性的变量在尝试改变其值的时候会报错。

class Person{
    public readonly name: string;
}

5.类的构造函数

普通的构造函数

typescript的类中有一个constructor方法,也就是我们的构造函数,我们可以使用这个方法对类进行初始化,而且typescript中的constructor写起来很方便,具体如下:

class Person {
    constructor(private name: string, public age: number) { }
}
const p = new Person("小黑", 21)
// console.log(p.name);     //这里记得name是私有变量,外部不能直接调用
console.log(p.age)

子类的构造函数

子类的构造函数比较特殊,由于进行了一次继承extends操作,所以我们在子类中写构造函数的时候必须使用super(),否则typescript会报错。

class Student extends Person {
    constructor(private exam: number) { 
        super("小白",18);
    }
}

6.类的setter和getter

在开发过程中,为了保证数据的安全性我们往往会把数据定义private,如果想要调用就会用到setter()方法与getter()方法或者构造函数方法。

class Person {
    constructor(private name: string, private age: number) { }
    get getName() {
        return this.name
    }
    set setName(name: string) {
        this.name = name
    }
    get getAge() {
        return this.age
    }
    set setAge(age: number) {
        this.age = age
    }
}
const p = new Person("小黑", 21)
p.setName = "小白"
console.log(p.getName)
image.png

7.抽象类的使用

抽象类abstract的引入能便于我们规范类的编写,上例子:
比如我们进出学校都需要报备自己是那个单位(班级)的。

abstract class School{
    // 抽象类中的方法都需要加上abstract关键字,表示抽象方法
    abstract baobei()
}
class Student extends School{
    // 继承了抽象方法的类都必须对父类中的抽象方法进行具体化,具体跟类的重写是一样的
    baobei(){
        console.log("我是学生,是xx班的")
    }
}

五、联合类型和类型保护

1.联合类型

当我们的一个方法可以接受多种类型的参数(parameter),那么此时就要用到联合类型

class Student {
    name: string;
    jiaozuoye() { };
}
class Teacher {
    name: string;
    gaizuoye() { };
}
function f(parameter: Student | Teacher) { }

2.类型保护

不过随着联合类型而来的还有一个问题,假如Student和Teacher中有两个不同的方法,不同类型的参数又怎么确定是否有继承该类型的特殊方法呢?为了解决这个问题,我们引入了类型保护的概念。

as语法

as又叫类型断言,as相当于是人为的判断,比如一个人的名字(name属性)就叫“学生”,那么我们判断他就是学生,让他能够调用学生的方法jiaozuoye(),具体操作如下。

function f(parameter: Student | Teacher) {
    if(parameter.name == "学生"){
        (parameter as Student).jiaozuoye();
    }
}

in语法

in语法比较好理解,比如我们的Student和Teacher中的区别就在于方法交作业和改作业的不同,如果参数有交作业的功能,那我们就判断他为Student,反之就判断他为Teacher,此时可以用in语法。

function f(parameter: Student | Teacher) {
    if("jiaozuoye" in parameter){
        parameter.jiaozuoye();
    }else{
        parameter.gaizuoye();
    }
}

typeof语法

上面我们举的例子中参数都是自定义的类型,那么对于普通类型我们的类型保护策略是什么呢?举一个新的例子

function f(parameter1: string | number, parameter2: string | number) {
//使用typeof进行类型判断
  if (typeof parameter1 === "string" || typeof parameter2 === "string") {
  //字符串拼接
    return `${first}${second}`;
  }
  return first + second;
}

instanceof语法

instanceof和typeof语法很像,但是instanceof只能用在类class的保护上。

// 我们先定义一个类,作为我们类型保护的基础
class NumberObj {
    count: number;
}
// 实现一个相加的方法
function f(first: object | NumberObj, second: object | NumberObj) {
    // 运用instanceof语句对类型进行判断
    if (first instanceof NumberObj && second instanceof NumberObj) {
        return first.count + second.count;
    }
    return 0;
}

六、泛型

对于typescript中的泛型,你可以把它理解为参数化类型,把每个参数的类型也封装成参数的形式,而这个封装的方式,就是泛型。

1.函数泛型

如果我们要编写一个函数,接收两个参数,这两个参数可能都为string类型或者都为number类型,最后做拼接操作。
对于这个功能,其实通过上面的联合类型以及类型保护就能实现,但是,我们再分析一下这个功能的条件,“这两个参数可能都为string类型或者都为number类型”,思考一下,我们能不能在调用的时候就直接告诉这个函数传进来的参数类型呢?答案是可以的,泛型就能实现这个功能。

function f<T>(one: T, two: T) {
    return `${one}${two}`
}
f<string>("ok", "fine")

这里再多提一嘴,由于typescript具有类型推断功能,所以我们在调用函数的时候甚至不需要写泛型中参数,比如这样:

f("ok", "fine")

当然,我们也可以定义多个泛型。

function f<T, P>(one: T, two: P) {
    return `${one}${two}`
}
f<string, number>("ok", 2)

2.类的泛型

初始化类的泛型

创建一个类,顺带把构造函数写好,两个参数:

class Student {
    constructor(private name: string, private age: number) { }
}

参数化参数的类型,实现对类的泛型初始化,之后实现一个简单的调用。

class Student<T, P> {
    constructor(private name: T, private age: P) { }
}
const stu = new Student<string, number>("小白", 21)

泛型的继承

接着上面Student的例子,现在有一个这样的数据结构:每一个Student都来源于一个Person接口,这个接口中有一个NAME属性(string类型),现在我们需要把一个Student对象中的NAME取出来。
首先我们完善一下interface以及getter函数的部分:

interface Person {
    NAME: string
}
class Student<T> {
    constructor(private name: T) { }
    get getName() {
        return this.name
    }
}

现在的接口Person和Student类还是没有任何关联的,接下来我们使用泛型的继承将Person中的NAME传递给Student中的name,大家在理解这一点的时候注意两个name的大小写,小写的是类中定义的,大写的是接口传递给类的

interface Person {
    NAME: string
}
class Student<T extends Person> {
    constructor(private name: T) { }
    get getName() {
        return this.name.NAME
    }
}

最后调用一下。

const stu = new Student({ NAME: "小白" })

如果大家观察的仔细,我们会发现我们在传递构造函数的时候直接传递进去了一个对象,是带着大括号的。原因其实很简单,我们可以将Person看作是一个要传进去的那个对象的描述,通过extends语句将这个描述传递给Student中的name属性,最后在我们初始化对象的时候,我们看似初始化的是一个对象,但其实我们初始化的还是构造函数中的那个name。
这个地方一开始看的我懵逼了很久,最后我照着别人的代码写了一个实例出来,又反复看了几遍就发现能看懂了。

上一篇 下一篇

猜你喜欢

热点阅读