进阶学习13:TypeScript——安装使用特性详解

Posted JIZQAQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶学习13:TypeScript——安装使用特性详解相关的知识,希望对你有一定的参考价值。

目录

一、导言

二、安装和使用

使用TypeScr的基本过程:

编译整个项目

三、原始类型和标准库

四、中文错误信息

五、作用域问题

方法一

方法二

六、Object类型

七、数值类型

八、元组类型(Tuple Types)

九、枚举类型

十、函数类型

十一、任意类型

十二、隐式类型推断

十三、类型断言

十四、接口Interfaces

十五、类

1.概述

2.使用

3.类和接口的合并使用例子

4.抽象类

十六、泛型

十七、类型声明


一、导言

TypeScript是一门在javascript之上的语言,也就是JavaScript的超集(superset)。它在JavaScript的原有基础之上,多了一些扩展特性。多出来的是一套更强大的扩展系统以及对ES的新特性的支持,最终会被编译为JavaScript。

解决了JavaScript类型系统的不足,用TypeScript可以大大提高代码的可靠程度。

任何一种JavaScript运行环境都支持用TypeScript来开发,而且它的功能更为强大,生态也更健全、更完善。Angular和Vue.js 3.0都是用的TypeScript,可以说TypeScript是前端的第二语言。

不过TypeScript也有缺点

  • 缺点一:这个语言本身多了很多概念,学习成本要比JavaScript高一些。不过好在TypeScript是渐进式的,我们完全可以先把它当做JS使用,再边学边用。
  • 缺点二:项目初期,TypeScript会增加一些成本,需要编写很多类型声明。如果是小项目的话可能会变得复杂,但是对于大型项目而言反倒是好事,可能是一劳永逸的事情。

TypeScript中文文档

https://www.tslang.cn/docs/home.html

二、安装和使用

命令行输入

npm install -g typescript

安装成功!

创建一个greeter.ts文件,在文件中输入:

function greeter(person) {
    return "Hello, " + person;
}

let user = "Jane User";
document.body.innerhtml = greeter(user);

然后命令行输入tsc greeter.ts运行,输出结果为一个greeter.js文件,它包含了和输入文件中相同的JavsScript代码。 

 一切准备就绪!

使用TypeScr的基本过程:

1.安装TypeScript模块,可以全局也可以本地

2.通过tsc命令编译ts文件,它会先检查我们文件中的类型使用异常。

3.移除掉一些类型注解之类的语法,并且自动转换ES的新特性。

编译整个项目

命令行运行

tsc --init

然后根目录下会新增一个tsconfig.json的文件,打开

其实可以看到,绝大部分的配置都被注释掉了。我们了解一下几个常用的选项。

  • ”target“:设置编译之后JavaScript使用的ES标准。
  • ”module“:输出的代码用什么方式模块化
  • ”outDir“:编译结果输出的文件夹
  • ”rootDir:源代码所在的文件夹
  • “sourceMap”:开启源代码映射
  • “strict”:开启所有严格检查选项,需要我们为每一个成员都指定明确的类型。不写的话就隐式推断为是Any,这是不被允许的要报错的。就算要Any也要自己写上去。

和上面的运行办法稍稍有点不同,直接输入tsc就可以直接运行了。

tsc

三、原始类型和标准库

//DEMO1
//原始数据类型
//strict配置成false的话,每个类型是允许传null的
const a: string = 'foobar'
const a1: string = null
const b: number = 100
const b1: number = NaN
const b2: number = Infinity
const b3: number = null
const c: boolean = true
const c1: boolean = null
const d: null = null
const e: undefined = undefined
//这里如果配置里面target是es2015以上的话就不会报错,但是我设置的es5就会报错.
//解决办法1:target设置为es2015
//解决办法2.仍然使用es5,但是引用ES2015的标准库,在配置文件里面新增"lib":["ES2015","DOM"]
const f: symbol = Symbol()

四、中文错误信息

TypeScript支持多语言的错误消息,会根据开发工具和操作系统选择语言。但是我自己的VSCode使用的是英文的,所以显示的也是因为英文的错误提示消息。

如:

//DEMO2
const error: string = 100

我们可以通过以下命令强制使用中文的提示消息

tsc --locale zh-cn

而VSCode里面的错误提示消息,可以通过Code——Preferences——Settings

搜索TypeScript locale,选择zh-CN来设置

但是并不推荐修改为中文,因为如果我们遇到问题想Google以下的话,中文的报错信息很难搜索出来什么有用的信息…建议大家开发相关的地方还是使用英文的报错提示。

五、作用域问题

可能会遇到不同文件中遇到相同变量名称的情况,

比如,我在两个文件中都声明了a变量,因为都是在全局声明的,所以会冲突。

 

解决办法就是把它们装在不同的作用域当中。

方法一

放入一个立即执行函数当中去

//DEMO3
//全局变量冲突
//const a: string = 'foobar'
//全局变量冲突解决办法:
//放在一个立即执行函数中去
(function (){
    const a = 123
})()

方法二

使用export导出一下,这样文件会作为模块,模块是有单独的模块作用域的

//方法二:使用export导出一下,这样文件会作为模块,模块是有单独的模块作用域的
const a = 123
export {} 

六、Object类型

TypeScript中的Object并不单指对象类型,而是泛指所有的非原始类型,也就是对象、数组、函数。

//DEMO4
//TypeScript的object指的是对象、数组、函数,而不是单单一个类型
const foo: object = function () {} // [] // {}
//如果只需要普通的对象,用类似对象字面量的方式,但是其实更好的办法是用接口的方式,这个以后再介绍
const obj: { foo: number, bar : string} = { foo:123, bar:'string' }

七、数值类型

//DEMO5
//数组类型
const arr1: Array<number> = [1,2,3]
const arr2: number[] = [1,2,3]
//使用强类型的好处
//使用了rest接受任意个数的输入参数,使用reduce方法计算总和,第一个参数是上次计算的结果,第二个参数是本次循环的当前值
function sum (...args:number[]) {
    //判断是不是数字
    return args.reduce((prev, current) => prev + current, 0)
}

八、元组类型(Tuple Types)

元组是一个明确元素数量以及每个元素类型的数据。各个元素类型不必相同。

//DEMO6
//元组类型
const tuple: [number,string] = [10, 'zce']
//访问某个元素
const age = tuple[0]
const name = tuple[1]
//或者解构的方式
const [age2, name2] = tuple
//ES2017 的entries方法
Object.entries({
    foo:123,
    bar:456
})

九、枚举类型

解释都写在注释里面了

//DEMO7
//枚举类型
const post = {
    title: 'Hello TypeScript',
    content: 'TypeScript is a typed superset of JavaScript.',
    status: 2 // 1 // 0 //如果用012直接写,可能时间长了我们就不记得分别代表什么含义
}
//以前我们可以通过这种办法来解决问题
const PostStatus = {
    Draft: 0,
    Unpublished: 1,
    Published: 2
}
//TypeScript有个枚举类型来解决,具体语法是enum
enum PostStatus2 {
    Draft = 0,
    Unpublished = 1,
    Published = 2
}
//使用方法 枚举名称+枚举成员名称
console.log(PostStatus2.Draft)
//枚举类型的值,可以不用等号指定,如果都没有设置,就会从0开始累加,如果像下面例子这样设置了第一项的值,那么后面都会在这个基础上累加
enum PostStatus3 {
    Draft = 6,
    Unpublished,
    Published
}
//不过如果是字符串枚举的话,需要给每一个都设置上字符串,没办法累加
enum PostStatus4 {
    Draft = 'DRAFT',
    Unpublished = 'UNPUBLISHED',
    Published = 'PUBLISHED'
}
//枚举类型会入侵我们运行时的代码,也就是会影响我们编译后的结果。别的TS代码基本会被移除掉,但是枚举会生成双向的键值对对象。
//生成双向的键值对对象主要是为了方便我们以PostStatus[0]这种模式使用,如果确认我们代码当中不会使用索引器去访问枚举,可以去使用常量枚举。就是前面加上const
const enum PostStatus5 {
    Draft,
    Unpublished,
    Published
}

 看一下我们生成的JS文件中,非常量枚举生成的双向键值对对象和常量枚举生成的结果。

//非常量的枚举
var PostStatus3;
(function (PostStatus3) {
    PostStatus3[PostStatus3["Draft"] = 6] = "Draft";
    PostStatus3[PostStatus3["Unpublished"] = 7] = "Unpublished";
    PostStatus3[PostStatus3["Published"] = 8] = "Published";
})(PostStatus3 || (PostStatus3 = {}));
//常量的枚举
var PostStatus4;
(function (PostStatus4) {
    PostStatus4["Draft"] = "DRAFT";
    PostStatus4["Unpublished"] = "UNPUBLISHED";
    PostStatus4["Published"] = "PUBLISHED";
})(PostStatus4 || (PostStatus4 = {}));

十、函数类型

函数的类型约束,就是对输入输出进行类型约束。JS中有两种函数定义方式:函数声明、函数表达式。我们需要了解这两种情况下,我们如何进行函数的类型约束。

//DEMO8
//函数类型
//参数后面添加?代表可选参数,可有可无,必须出现在参数列表的最后或者给参数设置默认值;
//也可以给函数设置默认值
//用...rest代表剩余参数
function func1(a:Number,b:number = 10,...rest:number[]):string {
    return 'func1'
}
//参数数目也要保证相同
func1(100,200)
func1(100)
func1(100,200,300)
//函数表达式
//接受函数的变量也是有类型的,可以使用类似箭头函数的方式表示
const func2:(a: number,b: number) => string = function (a:number, b:number):string{
    return 'func2'
}

十一、任意类型

//DEMO9
function striingify (value:any) {
    return JSON.stringify(value)
}

striingify('string')
striingify(100)
striingify(true)
//any是动态类型,和本身JS相同,TypeScript不会对any做任何类型检查
let foo: any = 'string'
foo = 100

十二、隐式类型推断

有的时候我们没有标记一个变量的类型,那么TypeScript会根据我们的使用推断一个类型。

比如下面这个例子,其实我们没有给age标记类型,但是根据我们的let age = 18,TS做了隐式推断,觉得age是属于number类型。于是我们后续再让age = 'string'的时候就报错了。

//DEMO10
//隐式推断
let age = 18
age = 'string'

还有就是声明的时候不做初始化,也不标记类型,这样会被隐式推断成any类型,后续赋任何类型的值都是可以的。但是并不推荐这么使用。

//这样呢就是把foo推断成了any类型,所以我们给他赋任何值都是可以的
let foo
foo = 100
foo = 'string'

十三、类型断言

例子里面有两种类型断言的方式,具体解释都写在代码的注释里面了。

//DEMO11
//类型断言
const nums = [110,120,119,112]
//虽然我们知道一定能找得到大于零的数组,但是TS不知道,他觉得我们有可能找不到,所以它觉得的res类型是数字或者undefined
const res = nums.find(i => i > 0)
//因此我们无法直接将res当做数字去使用
const square = res * res
//所以我们需要加上断言,告诉ts就是数字类型的
//方法一
const num1 = res as number
//方法二:但是这个方法有点小问题,会和JSX产生语法冲突,所以推荐使用方法一
const num2 = <number>res

*要注意的是,类型断言不是类型转换,断言是编译时候的概念运行时无作用,而类型转换的话运行时也会有作用。

十四、接口Interfaces

接口可以用来约定对象的结构,里面具体有什么成员,成员是什么样子的。一个对象去实现一个接口,它就必须拥有这个接口所约束的所有成员。

看一下下面例子:

//DEMO12
//这里可以使用逗号,也可以使用分号,也可以省略
interface Post{
    title:string,
    content:string,
    subtitle?: string,//可选成员
    readonly summary: string//只读成员
}
function printPost(post:Post){
    console.log(post.title)
    console.log(post.content)
}
printPost({
    title:'TITLE',
    content:"A javascript superset",
    summary:"A javascript"
})

//动态成员
interface Cache{
    //key可以是任意名称
    [key: string]:string
}
const cache:Cache = {}
cache.foo = 'value1'
cache.bar = 'value2'

但是使用tsc编译过后我们会发现,我们输出的JS文件中(下面就是输出文件的代码),并没有接口相关的任何代码,只是为了让我们对结构进行约束,实际运行阶段并没有意义。

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function printPost(post) {
    console.log(post.title);
    console.log(post.content);
}
printPost({
    title: 'TITLE',
    content: "A javascript superset",
    summary: "A javascript"
});
var cache = {};
cache.foo = 'value1';
cache.bar = 'value2';

十五、类

1.概述

类用来描述一类具体对象的抽象成员。ES6开始JavaScript中有了专门的class,TypeScript中增强了class的相关语法。

2.使用

下面例子是一些比较基础的用法的实例:

//DEMO13
//Class
class Person {
    //ts中必须先声明name和age,需要赋值,可以这里直接写初始值,也可以构造函数里面给值
    name: string = 'init name'
    //我们可以通过添加private这种访问修饰符,修饰成员属性,这样age以后只能在成员内部访问。
    private age: number
    //除了private还可以用public,但是不加默认就是public,不加也行。
    //还有protected修饰符,只允许在本身和子类当中访问的成员
    protected readonly gender: boolean
    
    constructor (name:string, age:number) {
        //直接使用this.name = name的话,会报Property 'name' does not exist on type 'Person'.的错
        //TS中我们需要明确声明对象中拥有的属性,而不是直接在构造函数中动态通过this添加
        this.name = name
        this.age = age
        this.gender = true
    }
    sayHi(msg:string):void {
        console.log(`I am ${this.name}, ${msg}`)
        console.log(this.age)
        console.log(this.gender)
    }
}

const tom = new Person('tom',13)
console.log(tom.name)
console.log(tom.age)//会报错,因为我们把age设置为私有属性,只能内部访问。
console.log(tom.gender)//会报错,使用protected修饰符之后,也无法进行外部访问

class Student extends Person {
    //可以把一个构造函数设置为private,但是外部无法实例化也无法被继承,只能通过在内部添加静态方法来创建
    private constructor (name:string,age:number){
        super(name,age)
        console.log(this.gender)//不会报错,使用protected修饰符之后,在子类中可以访问
        this.gender = false//会报错,因为gender是readonly属性,初始化成功之后就不能修改了。
    }
    static create (name:string,age: number){
        return new Student(name,age)
    }
}

const jack = new Student()//会报错,因为student的构造函数是private的,外部无法直接实例化,需要通过使用它的静态函数
const john = Student.create('john',18)//OK

详细的都写在了代码的注释里面,我这边只总结了其中几点:

  • 我们可以通过添加访问修饰符,修饰成员属性,访问修饰符有3中,分别是public、private和protected。public就是默认的,private呢只能在内部访问,protected是只能在内部和子类中访问。
  • 可以在成员后面添加?,代表是一个可选成员。
  • 在成员前面添加readonly代表只读,如果有访问修饰符的话,readonly放在访问修饰符后面。
  • 我们也能够在构造器前面添加private,但是这样之后我们无法在外部将其实例化,不过可以通过在内部添加静态方法,然后在外部调用静态方法的方式来解决。

3.类和接口的合并使用例子

可以看一下下面这个例子,两个类用了相同的能力,但是每个能力具体实现稍微有点不用。我们用接口把类里面的能力给抽象出来。

//DEMO14
// 类与接口
interface Eat {
    eat (food: string): void
}

interface Run {
    run (distance: number): void
}

class Person implements Eat, Run {
    eat (food: string): void {
        console.log(`优雅的进餐: ${food}`)
    }

    run (distance: number) {
        console.log(`直立行走: ${distance}`)
    }
}

class Animal implements Eat, Run {
    eat (food: string): void {
        console.log(`呼噜呼噜的吃: ${food}`)
    }

    run (distance: number) {
        console.log(`爬行: ${distance}`)
    }
}
  

4.抽象类

抽象类在某种程度上和接口有点类似,可以用来约束子类当中必须有什么成员,但是它可以包含一些具体实现,而接口不包括具体实现。

//DEMO15
//抽象类
//被定义为抽象类过后只能被继承,无法通过new创建实例对象。
abstract class Animal {
    eat (food:string): void{
        console.log(`呼噜呼噜的吃: ${food}`)
    }

    abstract run (distance: number):void
}

class Dog extends Animal {
    run(distance:number):void {
        console.log(`爬行: ${distance}`)
    }
}

const d = new Dog()
d.eat('狗粮')
d.run(100)

十六、泛型

泛型就是指定义函数、接口或类的时候不去指定具体类型,使用的时候才去指定具体类型的特征。这是为了极大程度的复用我们的代码。

//DEMO16
//TypeScript泛型
//这个函数用来创建一个指定长度的数组。
function createNumberArray (length:number,value:number):number[] {
    const arr = Array<number>(length).fill(value)
    return arr
}
//假设我们还需要一个string的数组,笨办法就是再写个string的函数
function createStringArray (length:number,value:string):string[] {
    const arr = Array<string>(length).fill(value)
    return arr
}
//或者使用泛型,不确定的都用T来代表
function createArray<T> (length:number,value:T):T[] {
    const arr = Array<T>(length).fill(value)
    return arr
}
const res = createNumberArray(3,100)

十七、类型声明

这是为了使用的时候单独为一些没有声明的做出声明,我们可以尝试去安装一个类型声明模块,如果没有对应的类型声明模块的话再尝试去通过类似declare function camelCase (input: string): string这样的模式来自己写。

//DEMO17
//类型声明
//这是为了使用的时候单独为一些没有声明的做出声明

//报错:Cannot find module 'lodash' or its corresponding type declarations.找不到类型声明文件
//通过npm install @types/lodash 解决的话就不需要我们自己手写declare了
import { camelCase } from 'lodash'

//declare function camelCase (input: string): string
//单用下面这句的话,camelCase没有任何类型提示,所以我们需要自己写上面这行
const res = camelCase('hello typed')


 

以上是关于进阶学习13:TypeScript——安装使用特性详解的主要内容,如果未能解决你的问题,请参考以下文章

进阶学习12:Flow——JavaScript的类型检查器(安装&使用Flow详解)

进阶学习12:Flow——JavaScript的类型检查器(安装&使用Flow详解)

开始学习TypeScript

TypeScript入门知识二(参数新特性)

进阶学习9:ECMAScript——概述ES2015 / ES6新特性详解

TypeScript入门学习笔记