为什么要使用TypeScript
首先了解一些强类型语言和弱类型语言:
强类型语言:语言层面限制函数的实参类型必须与形参相同,而且不允许任何隐式类型的转换。
弱类型语言:不限制实参类型,允许任意类型转换。
显然,JS就是一门弱类型的语言,弱类型的语言很容易引起类型安全的问题。
弱类型语言,类型安全问题例子:因为add函数中的a与b参数没有限制类型,本身这个方法是一个两个数字相加求和的方法,但是一旦传入字符串,得到的结果就不尽人意。
function add(a, b) {
return a + b
}
add(100, 100) // =>200
add(100, '100') //=>100100
再来了解一下静态类型语言,与动态类型语言:
静态类型:一个变量声明时,类型就是明确的,后面不能改变其类型。
动态类型:运行时类型才明确,后续可以改变其类型。
显然,JS是一门动态类型的语言。
早期的JS应用比较简单,往往只需要几十行代码就搞定了,所以在当时看来类型系统显得非常多余。其次JS是脚本语言,不需要编译,直接在环境中运行,所以设计成静态语言也没意义,因为静态语言会在编译阶段做类型检查。但是,目前随着web应用的不断发展,前端应用规模变大了,代码越来越复杂,类型安全的问题也越来越突出,所以TypeScript就出现了。
TS是JS的超集,里面囊括了JS的语法,一套完整的类型系统,以及支持最新的JS语法,并且在编译阶段,可以根据需要转换成对应版本的JS,最低支持到ES3的语法。那么这个转换语法的过程,就省去了babel的转换过程。
TypeScript原始类型
先来回顾一下在JS中有哪几种原始类型。
number
string
null
undefined
boolean,
以及ES6新增的Symbol。
那么在TypeScript中也不例外,直接上代码。
// 原始数据类型
const a: string = "foobar"
const b: number = 100 // NaN Infinity
const c: boolean = true // false
//在非严格模式(strictNullChecks)下,
//string, number, boolean 都可以为空
// const d: string = null
const e: void = undefined
const f: null = null
const g: undefined = undefined
const h: symbol = Symbol()
那么显然TS的中的原始类型与JS几乎一模一样的,在上面的代码看来,语法上只需要在定义的变量名后面加一个冒号以及要定义的类型。有一点需要大家了解的就是,在严格模式下,String,Number,Boolean类型的变量不允许为null。特别注意Symbol类型的使用,需要在配置文件中修改相关引入的模块,这个在后面的配置文件中会详细介绍。
object类型
在TS中,如果将一个变量定义为object类型,注意这里的object的o字母是小写,这里的object类型包括了,数组,函数也就是除了原始对象外的其它类型。
const foo: object = function () {} // [] // {}
那么如果非要定义一个普通对象,且要对其属性加以限制呢?
const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' }
在上面这行代码中,只是简单地定义了一个obj对象,以及限制它的两个属性名,以及属性的类型,要求第一个属性名必须为‘foo’,类型必须为number,第二个属性名为‘bar’,类型必须为string。那么如果这个对象中有多个属性,某个属性可有可无,某些属性又必须存在该如何定义呢?这个问题我们会在TS中的接口详细描述。
数组类型
数组类型的定义,有两种方法:
// 数组类型的两种表示方式
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
例子:如果我们编写一个多个数字相加的函数,在JS中我们需要这么写,要求写一个循环,并且要求每个参数必须为数字类型。
function sum (...args) {
for (let i = 0; i < args.length; i++) {
if (typeof args[i] !== 'number' && isNaN(args[i])) {
throw TypeError('类型错误')
}
}
return args.reduce((prev, current) => prev + current, 0)
}
console.log(sum1(1, 2, 3))
那么在TS中,我们只需要这么写就可以了:使用 : number[] 就必须要求数组中每一个元素必须为数字类型。
function sum (...args: number[]) {
return args.reduce((prev, current) => prev + current, 0)
}
元组类型
元组:数量确定,元素类型确定的数组
const tuple: [number, string] = [18, 'zce']
定义一个数组,数组中指定只有两个元素,第一个元素必须是number类型,第二个元素必须是string类型。
其实元组类型在JS中也有体现:
const entries: [string, number][] = Object.entries({
foo: 123,
bar: 456
})
const [key, value] = entries[0]
那么在上面代码中,指定了变量entries是一个数组,要求:数组中的每一个元素也是一个只有两个元素的数组,且第一个元素为string类型,第二个元素为number类型。然后使用Objec.entries将一个对象转换成数组。使用Objec.entries返回的结果数组中,每一个元素就是一个数组。
枚举类型
什么是枚举类型?引用C++菜鸟教程中的一句话:枚举类型(enumeration)是 C++ 中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
举个例子可能会更加明白:目前正在做一个文章管理的系统项目,那么文章有三个状态:草稿状态,未出版,已出版。那么我们分别用0,1,2去表示这三个状态:
const PostStatus = {
Draft: 0,
Unpublished: 1,
Published: 2
}
看到这里大家应该会很了解,因为上面这种类似于对某个事物固定几个状态的描述,在JS中也会像以上几行代码那样,使用一个对象去描述事物的状态。那么在TS中,提供了枚举类型,更加能够清晰地描述事物地状态。
const enum PostStatus {
Draft,
Unpublished,
Published
}
注意一点就是,以上Draft,Unpublished,Published我并没有设定对应的枚举值,那么在TS中的枚举类型中,如果未设置枚举值的情况下,默认从0开始递增,于是上面的代码等价于:
enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2
}
其次,在数组枚举的时候,枚举值会基于前一个枚举值自增。
除了数字枚举,还有字符枚举:
// 字符串枚举
enum PostStatus {
Draft = 'aaa',
Unpublished = 'bbb',
Published = 'ccc'
}
关于枚举类型,还有一点大家必须要注意,常量枚举,不会侵入编译后的结果。
使用常量类型的枚举,也就是使用const声明枚举类型:
const enum PostStatus {
Draft = "a",
Unpublished = "b",
Published = "c",
}
const post = {
title: "Hello TypeScript",
content: "TypeScript is a typed superset of JavaScript.",
status: PostStatus.Draft, // 3 // 1 // 0
}
// -----------------编译后的JS文件---------------------------------
Object.defineProperty(exports, "__esModule", { value: true });
var post = {
title: "Hello TypeScript",
content: "TypeScript is a typed superset of JavaScript.",
status: "a" /* Draft */,
};
不使用const声明枚举类型:在编译后的文件中,多了一段代码,描述了枚举名以及枚举值的关系
enum PostStatus {
Draft = "a",
Unpublished = "b",
Published = "c",
}
const post = {
title: "Hello TypeScript",
content: "TypeScript is a typed superset of JavaScript.",
status: PostStatus.Draft, // 3 // 1 // 0
}
// -----------------------编译后的JS文件-------------------------------
var PostStatus;
(function (PostStatus) {
PostStatus["Draft"] = "a";
PostStatus["Unpublished"] = "b";
PostStatus["Published"] = "c";
})(PostStatus || (PostStatus = {}));
var post = {
title: "Hello TypeScript",
content: "TypeScript is a typed superset of JavaScript.",
status: PostStatus.Draft,
};
函数类型
直接上代码:
function func1 (a: number, b: number): string {
return 'func1'
}
上面代码,括号中表示此函数仅接收两个参数,第一个为number类型,第二个也为number类型,在括号的后面有 : string 表示此函数的返回类型必须为string类型的。
那么如何表示某个参数可有可无呢?有两种方法:
第一种:给参数加默认值,如下代码,给b设置了一个为10的默认值。
function func1 (a: number, b: number = 10): string {
return 'func1'
}
第二种:在某个可有可无的参数后面加一个’?’
function func1 (a: number, b?: number): string {
return 'func1'
}
在一些高阶函数当中,常常把函数当作一个变量,应该这样定义:
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
return 'func2'
}
定义了func2为一个变量,这个变量要求是一个函数,且第一个第二个值都为number类型,且最终返回string类型。
任意类型
在TS中使用any表示任意类型。在以下代码中,JSON.stringify本身可以接收任何类型的参数,使用any类型,TS就不会这个数据进行类型检测。使用了any类型的数据,就相当于是一个动态类型,就可以像JS一样随意更改这个数据类型。所以,在开发的过程当中应当尽量避免使用any类型,避免造成类型安全问题。
function stringify (value: any) {
return JSON.stringify(value)
}
接口
什么是接口?简单来说,定义某个值的结构:
interface Post {
title: string
content: string
}
// --------------使用----------------
function printPost (post: Post) {
console.log(post.title)
console.log(post.content)
}
在printPost 函数中,必须传入一个对象,这个对象的格式必须按照所定义的Post接口传入。
那么以上Post接口中,某个属性可有可无,同样地,在属性名后面加个 '?'表示,某个属性只读不可修改,就在属性名前面加一个readonly表示。
interface Post {
title: string
content: string
subtitle?: string
readonly summary: string
}
其次实现动态接口:
interface Cache {
[prop: string]: string
}
这个使用了这个接口的对象,要求键必须为string类型,值也必须为string类型
泛型
泛型:我们把定义时不能够确定明确的类型变成一个参数,使用时再传递。
function createNumberArray (length: number, value: number): number[] {
const arr = Array<number>(length).fill(value)
return arr
}
function createStringArray (length: number, value: string): string[] {
const arr = Array<string>(length).fill(value)
return arr
}
上面的两个方法,功能其实一模一样,但是因为传入的数据类型不一样,返回值的类型不一样,所以写成了两个方法。使用泛型,可以合并为一种方法。
function createArray<T> (length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const res = createArray<string>(3, 'foo')
上面代码中的T就是泛型,可以作为参数来传递