这是我参与「第四届青训营 」笔记创作活动的第9天
认识Typescript
编程语言的类型
1.动态类型语言(Dynamically Typed Language)
特点:运行期间进行数据类型检查
在使用动态类型语言编程期间,不用给变量指定数据类型,如:JavaScript、Python、Ruby
2.静态类型语言(Statically Typed Language)
特点:编译阶段进行数据类型检查
在使用静态类型语言编程期间,需要手动的指定数据类型,如:C、C++、C#、Java
什么是Typescript?
Typescript发展史
为什么是typescript?
可以理解为可扩展的JavaScript,即JavaScript 的超集。
提供了:
- 静态类型风格的类型系统
- 从 ES6 ~ ES10 甚至是 exnext 的语法支持
- 兼容各种浏览器,各种系统,各种服务器,完全开源
优点:
- 程序更容易理解
- 效率更高
- 更少的错误
安装typescript
typescript官网:www.typescriptlang.org/zh/
npm i -g typescript
2.使用tsc全局命令
//查看tsc版本
tsc -v
//编译ts文件
tsc fileName.ts
基本语法
原始数据类型和Any类型
原始类型:
- Number
- String
- Boolean
- Undefined
- Null
- BigInt(ES6)
- Symbol(ES6)
any类型: 允许赋值为任意类型,可以访问任何属性和方法(如果有明确的类型应避免使用这个类型)
数组和元组
数组(Array)
声明数字类型的数组:
let arrOfNumbers:number[] = [1,2,3]
元组(Tuple)
即特殊类型的数组,可以使用数组的api,可以自定义多种数据类型,但新增的数据必须是已经定义好的数据类型
let user:[string,number] = ['viking',20]
Interface 接口
- 用于对「对象的形状(Shape)」进行描述
- 用 implements 的方法来抽象 类的属性和方法
- 定义函数类型
- Duck Typing(鸭子类型):是动态编程语言的一种对象推断策略,它更关心对象如何被使用而不是对象本身
Interface是不存在于JavaScript中的概念,所以在ts编译后不会被转换过去,只能做类型的静态检查
interface Person {//用interface关键字定义了一个接口
readonly id: number //readonly代表只读属性
name: string,
age?: number //属性后跟?代表是可选参数
}
let viking: Person = {
id = 1,
name: 'viking'
age:22 //可以不声明
}
Function 函数
- 输入:声明参数的类型
- 输出:声明返回值类型
- 可选参数:在参数后加 ? (注:可选参数后面不要加确定参数,不然程序对参数的判断会发生混乱)
最后一行的这个箭头并不是箭头函数,而是ts中声明函数类型返回值的方法
- interface描述函数类型
类型推论、联合类型、类型断言
类型推论:
在没有明确指定类型的时候推测出一个类型
let str = 'str';
联合类型:
可以指定多种类型(但只能访问联合类型所有类型里共有的属性或方法)
let numberOrString:number | string
类型断言:
TypeScript 类型断言用来告诉编译器你比它更了解这个类型
不是类型转换,断言成一个联合类型中不存在的类型是会出现错误的
在不确定类型的时候访问其中一个类型的属性或方法
1.用as指定类型
只能指定联合类型中有的类型
// 这里我们可以用 as 关键字,告诉typescript 编译器,
//你没法判断我的代码,但是我本人很清楚,
//这里我就把它看作是一个 string,你可以给他用 string 的方法。
function getLength(input: string | number): number {
const str = input as string
if (str.length) {
return str.length
} else {
const number = input as number
return number.toString().length
}
}
2.类型守卫(type-guard)
当遇到一个联合类型的时候,使用条件语句,它可以自动帮你来缩小类型的范围
// typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
function getLength2(input: string | number): number {
if (typeof input === 'string') {
return input.length
} else {
return input.toString().length
}
}
枚举(Enums)
数字枚举,一个数字枚举可以用enum
这个关键词来定义,我们定义一系列的方向,然后这里面的值,枚举成员会被赋值为从 0 开始递增的数字
- 枚举默认从0开始赋值,举里的项会自动递增(+1),若手动赋值,则会接着手动赋值的枚举项递增
- 如果某个属性赋值,其他属性也要赋值,否则报错
- js的赋值语句返回被赋的值
- 定义枚举时加const则为常量枚举,常量枚举可提升性能
enum Direction {
Up,
Down,
Left,
Right,
}
console.log(Direction.Up)
// 还有一个神奇的点是这个枚举还做了反向映射
console.log(Direction[0])
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
console.log('go up!')
}
泛型(Generics)
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
可以先看做一个占位符,在使用时才动态控制其类型
function echo(arg) {
return arg
}
const result = echo(123)
// 这时候我们发现了一个问题,我们传入了数字,但是返回了 any
//<T>为泛型 名字可自定义
function echo<T>(arg: T): T {
return arg
}
const result = echo(123)
// 泛型也可以传入多个值
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result = swap(['string', 123])
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function echoWithArr<T>(arg: T): T {
console.log(arg.length)
return arg
}
// 上例中,泛型 T 不一定包含属性 length,我们可以给他传入任意类型,\
//当然有些不包括 length 属性,那样就会报错
interface IWithLength {
length: number;
}
//泛型约束 通过 extends 关键字来设置约束条件,而不是想传入啥就传入啥
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
echoWithLength('str')
const result3 = echoWithLength({length: 10})
const result4 = echoWithLength([1, 2, 3])
泛型与类和接口
1.创建一个拥有特定类型的容器,比如:class 和interface上的泛型仿佛给你一个容器贴标签一样,比如:
let arrTwo:Array<number>=[1,2,3]
给这个数组贴了一个number的标签,告诉arrTwo,是一个装满number类型的数组或者是装着数字的队列
2.灵活约束参数的类型,传入的参数必须是xx方法xx属性,否则就会报错
3.函数的使用,函数的类型推断不会流入到函数体内,所以使用表达式没法建立类型的绑定,用泛型可以打破这个鸿沟
class Queue {
private data = [];
push(item) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed())
//在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,
//当然,当数据被弹出队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据,
//但是那么在使用的过程中,就会出现我们无法捕捉到的错误,
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}
const queue = new Queue<number>()
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}
类型别名,字面量和交叉类型
1.类型别名
就是给类型起一个别名,让它可以更方便的被重用。
关键字:type
let sum: (x: number, y: number) => number
const result = sum(1,2)
type PlusType = (x: number, y: number) => number
let sum2: PlusType
// 支持联合类型
type StrOrNumber = string | number
let result2: StrOrNumber = '123'
result2 = 123
2.字面量
常量作为类型写在冒号后面,此时冒号前面的变量只能赋值为该常量;
const str:'name' = 'name'//只能等于name 否则报错
const number:1 =1
字面量可以用 | 设置多个,形成固定的赋值范围
// 字符串字面量
type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Directions = 'Up'//只能为其中的四个值
3.交叉类型 (Intersection Types)
对Interface进行扩展 &
interface IName {
name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: 'hello', age: 12}