初学TypeScript

为什么要使用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就是泛型,可以作为参数来传递

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值