学习TypeScript

什么是TypeScript

https://www.tslang.cn/

TypeScript是Microsoft公司注册商标。

TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

安装TypeScript

1、在全局环境里安装TS

$ cnpm install -g typescript
$ tsc -v
# Version 4.9.4

2、用 tsc 命令编译 .ts 文件

src/01app.ts 文件:

// src/01app.ts
let title: string = '千锋教育'

在命令行里输入以下命令都可以将.ts文件编译为.js文件:

# 使用tsc指令将src文件夹下的 01app.ts 文件转换输出到 dist目录下的app.js
$ tsc ./src/01app.ts --outFile ./dist/app.js

5分钟了解TypeScript

构建第一个TypeScript文件

在编辑器,将下面的代码输入到 src/02greeter.ts 文件里。我们注意到 person: string,表示 string 是 person 函数的参数类型注解:

// src/02gretter.ts
// 请问该函数有返回值吗?是什么数据类型
// 请问该函数有没有参数,参数是什么数据类型
// 参数为string类型,函数的返回值为string类型
function greeter (person: string): string {
   
  return 'hello' + person
}

const person = '吴大勋'

greeter(person)

类型注解

TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。 在这个例子里,我们希望 greeter函数接收一个字符串参数。 然后尝试把 greeter的调用改成传入一个数组:

// src/02gretter.ts
// 请问该函数有返回值吗?是什么数据类型
// 请问该函数有没有参数,参数是什么数据类型
// 参数为string类型,函数的返回值为string类型
function greeter (person: string): string {
   
  return 'hello' + person
}

// const person = '吴大勋'
// greeter(person)

const user = [0, 1, 2]
greeter(user)

重新编译,你会看到产生了一个错误:

Argument of type 'number[]' is not assignable to parameter of type 'string'.
// src/02gretter.ts
// 请问该函数有返回值吗?是什么数据类型
// 请问该函数有没有参数,参数是什么数据类型
// 参数为string类型,函数的返回值为string类型
// function greeter (person: string): string {
   
//   return 'hello' + person
// }

// const person = '吴大勋'
// greeter(person)

// const user = [0, 1, 2]
// greeter(user)

// person: (person: string) => string  ===> 说明函数的返回值为sting类型
// function (person: string): string ===》  说明函数的返回值为sting类型
// const greeter: (person: string) => string = function (person: string): string {
   
//   return 'hello' + person
// }

// (person: string) => string   => 说明的是函数的返回值类型
// 后面的 => 代表的函数的箭头函数
// const greeter = () => {}
const greeter: (person: string) => string = (person: string): string => {
   
  return 'hello' + person
}

类似地,尝试删除greeter调用的所有参数。 TypeScript会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。

要注意的是尽管有错误,greeter.js文件还是被创建了。 就算你的代码里有错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。

接口

让我们开发这个示例应用。这里我们使用接口来描述一个拥有firstName和lastName字段的对象。 在TypeScript里,只在两个类型内部的结构兼容,那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements语句。

// src/03index.ts

// 接口的首字母大写
// 一般以I开头
// 接口不是js的对象,元素之间需要使用; 或则 ,隔开或者是可以不用写
interface IPerson {
    // 接口
  firstName: string
  lastName: string
}

const greeter1 = (person: IPerson): string => {
   
  return 'hello' + person.firstName + person.lastName
}

const person1 = {
    firstName: '吴', lastName: '大勋' }

greeter1(person1)

最后,让我们使用类来改写这个例子。 TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。让我们创建一个Student类,它带有一个构造函数和一些公共字段。 注意类和接口可以一起工作。

还要注意的是,在构造函数的参数上使用public等同于创建了同名的成员变量。

***注:***public修饰符会引发 Parameter 'firstName' implicitly has an 'any' type.,解决方法是在tsconfig.json文件中,添加"noImplicitAny": false,或者将"strict": true,改为"strict": false

// src/04student.ts
// constructor 如果有 public 字样,表示自动给代码添加了 相应的成员变量
class Student {
   
  fullName: string;
  // fistName: string;
  // lastName: string;
  constructor (public firstName: string, public lastName: string) {
   
    this.fullName = this.firstName + this.lastName
  }
}

interface IPerson {
   
  firstName: string;
  lastName: string;
}

function greeter2 (person: IPerson) {
   
  return 'hello' + person.firstName + person.lastName
}

let user = new Student('吴', '大勋')

greeter2(user)

基础类型-入门

介绍

TypeScript包含的最简单的数据单元有:数字,字符串,布尔值,Null 和 Undefined等。TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。本节介绍基础类型的布尔值、数字、字符串、数组、元组、枚举、any 和 void 等,其他几种基础类型详见 基础类型-高级

布尔值

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean

let isDone: boolean = false

数字

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是number

let decLiteral: number = 6

字符串

TypeScript像其它语言里一样,使用string表示文本数据类型。 和JavaScript一样,可以使用双引号(")或单引号(')表示字符串。

let from: string = "千锋教育"
from = "好程序员"

也使用模版字符串,定义多行文本和内嵌表达式。 这种字符串是被反引号包围(`),并且以${ expr }这种形式嵌入表达式。

let surname: string = `Felix`
let age: number = 37
let sentence: string = `Hello, my name is ${
      surname }.

I'll be ${
      age + 1 } years old next month.`

数组

TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

let list: number[] = [1, 2, 3]

第二种方式是使用数组泛型,Array<元素类型>:

let list: Array<number> = [1, 2, 3]

// arr.ts

// src/05arr.ts
let arr: number[] = [1, 2, 3]
let arr1: Array<number> = [ 4, 5, 6] // 泛型

interface ICar {
   
  brand: string
  // list: string[] // Array<string>
  list: (string | number)[] // Array<string | number>
}

let arr2: ICar[] = [ // Array<ICar>
  {
   
    brand: '宝马',
    // list: ['X6', 'M4']
    list: ['X6', 'M4', 1]
  },
  {
   
    brand: '奔驰',
    // list: ['A系', 'G系']
    list: ['A系', 'G系', 1]
  }
]

元组 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。

// 声明一个元组类型 x
let x: [string, number]
// 初始化 x
x = ['hello', 10] // OK
// 无效的初始值
x = [10, 'hello'] // Error

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)) // OK
console.log(x[1].substr(1)) // Error, 'number' 不存在 'substr' 方法

当访问一个越界的元素,会出现错误:

x[3] = "world" // Error, '[string, number]' 未定义第 3 个元素的类型.
console.log(x[5].toString()) // Error, '[string, number]' 未定义第 5 个元素的类型.
// src/06tuple.ts
// 元组 不仅限定了数据类型 而且还限定了哪一个位置的上的数据类型

let x: [string, number, boolean]

x = ['hello', 1, true]  // ✅

// x = [1, 'hello', false]  // ❌

// 当访问一个已知索引值的元素时,会得到正确的数据类型
console.log(x[0].substr(1)) // ✅
// console.log(x[1].subStr(1)) // ❌  类型“number”上不存在属性“subStr”。

// 当访问一个越界的元素,报错
// 不能将类型“"world"”分配给类型“undefined”。
// 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
x[3] = 'world'

枚举

enum类型是对JavaScript标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字。

enum Color {Red, Green, Blue}
let c: Color = Color.Green

默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1 开始编号:

enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green

或者,全部都采用手动赋值:

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green

枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName)  // 'Green'

//enum.ts

// src/07enum.ts
// enum类型是对JavaScript标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字
// 周一-周日
// 1月-12月
// 上右下左

// enum Color { Red, Green, Blue }
// // 默认情况下,从 0 开始为元素编号
// let a: Color = Color.Red
// let b: Color = Color.Green
// let c: Color = Color.Blue

// console.log(a)// 0
// console.log(b)// 1
// console.log(c)// 2

// enum Color { Red = 11, Green, Blue }
// let a: Color = Color.Red
// let b: Color = Color.Green
// let c: Color = Color.Blue

// console.log(a)// 11
// console.log(b)// 12
// console.log(c)// 13


// enum Direction { 
//   UP = 38,
//   RIGHT = 39,
//   DOWN = 40,
//   LEFT = 37
// }

// 不仅可以复制索引值,也可以赋值字符串
enum Week {
   
  Monday = '星期一',
  Tuesday = '星期二',
  Wednesday = '星期三',
  Thursday = '星期四',
  Friday = '星期五',
  Saturday = '星期六',
  Sunday = '星期日'
}

// let w: Week = Week.Friday
let w: Week = Week['Friday'] // []内部变量

any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用any类型来标记这些变量:

let notSure: any = 4
notSure = "maybe a string instead" // OK 赋值了一个字符串
notSure = false // OK 赋值了一个布尔值

在对现有代码进行改写的时候,any 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。

let notSure: any = 4
notSure.ifItExists() // okay, ifItExists函数在运行时可能存在
notSure.toFixed() // okay, toFixed 函数存在 (在编译时不做检查)

当你只知道一部分数据的类型时,any 类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:

let list: any[] = [1, true, "free"]
list[1] = 100

void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

function echo(): void {
  console.log('做真实的自己,用良心做教育')
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:

let unusable: void = undefined
let greeting: void = 'hello world' // void 类型不能赋值为字符串

类型的断言

// src/08assert.ts
// a as b   a 断言为 b数据类型

// let val: string = 'hello world'
// let val = '10000'

// let len = val.length // 类型“number”上不存在属性“length”。
// let len = (val as string).length // 找不到名称“val”。你是否指的是“eval”?

// let len = (<string>val).length


函数的类型

函数声明

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):

// 函数声明(Function Declaration)
function sum(x, y) {
  return x + y
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
  return x + y
}

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:

function sum(x: number, y: number): number {
   
  return x + y
}

注意,输入多余的(或者少于要求的)参数,是不被允许的

function sum(x: number, y: number): number {
   
  return x + y
}
sum(1, 2, 3)

// Expected 2 arguments, but got 3.
function sum(x: number, y: number): number {
   
  return x + y
}
sum(1)

// An argument for 'y' was not provided.

函数表达式

如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:

let mySum = function (x: number, y: number): number {
   
  return x + y
}

这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
   
  return x + y
}

注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

在 ES6 中,=> 叫做箭头函数,应用十分广泛,可以参考 [ES6 中的箭头函数][]。

用接口定义函数的形状

我们也可以使用接口的方式来定义一个函数需要符合的形状:

// fn.ts

interface SearchFunc {
   
  (source: string, subString: string): boolean
}

let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
   
  return source.search(subString) !== -1
}

可选参数

前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用 ? 表示可选的参数:

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
  return firstName + ' ' + lastName
  } else {
  return firstName
  }
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName('Tom')

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了

function buildName(firstName?: string, lastName: string) {
  if (firstName) {
  return firstName + ' ' + lastName
  } else {
  return lastName
  }
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName(undefined, 'Tom')

// A required parameter cannot follow an optional parameter.

参数默认值

在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数

function buildName(firstName: string, lastName: string = 'Cat') {
  return firstName + ' ' + lastName
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName('Tom')

此时就不受「可选参数必须接在必需参数后面」的限制了:

function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName
}
let tomcat = buildName('Tom', 'Cat')
let cat = buildName(undefined, 'Cat')

关于默认参数,可以参考 [ES6 中函数参数的默认值][]。

剩余参数

ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数):

function push(array, ...items) {
  items.forEach(function(item) {
  array.push(item)
  })
}

let a = []
push(a, 1, 2, 3)
console.log(a) // [1, 2, 3] 

事实上,items 是一个数组。所以我们可以用数组的类型来定义它:

function push(array: any[], ...items: any[]) {
  items.forEach(function(item) {
  array.push(item)
  })
}

let a = []
push(a, 1, 2, 3)
console.log(a)

注意,rest 参数只能是最后一个参数,关于 rest 参数,可以参考 [ES6 中的 rest 参数][]。

重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'

利用联合类型,我们可以这么实现:

function reverse(x: number | string): number | string {
   
  if (typeof x === 'number') {
   
  return Number(x.toString().split('').reverse().join(''))
  } else {
   
  return x.split('').reverse().join('')
  }
}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

这时,我们可以使用重载定义多个 reverse 的函数类型:

// function reverse(x: number | string): number | string {
   
//   if (typeof x === 'number') {
   
//   return Number(x.toString().split('').reverse().join(''))
//   } else {
   
//   return x.split('').reverse().join('')
//   }
// }

// 重复定义了多次函数 `reverse`,前几次都是函数定义,最后一次是函数实现
// TypeScript 会优先从最前面的函数定义开始匹配,
// 所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面
// function reverse(x: number): number
// function reverse(x: string): string
// function reverse(x: number | string): number | string {
   
//   if (typeof x === 'number') {
   
//   return Number(x.toString().split('').reverse().join(''))
//   } else {
   
//   return x.split('').reverse().join('')
//   }
// }

function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
   
  if (typeof x === 'number') {
   
  return Number(x.toString().split('').reverse().join(''))
  } else {
   
  return x.split('').reverse().join('')
  }
}

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

// src/09fn.ts
// 函数 函数声明  函数表达式
// function fn (params): void {
   
// }

// const fn: (x: number, y: number) => number = (x: number, y: number): number => {
   
//   return x + y
// }

// 用接口定义了函数的形状
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值