TypeScript学习总结(一)
1、阅读本文章,需要具备的知识技能:
2、阅读本文章,需要安装的环境和工具:
一、什么是TypeScript?
TypeScript是JavaScript类型的超集,它可以编译成JavaScript代码。
TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
TypeScript是JavaScript类型的超集的意思是,TypeScript在ES6基础上做了升级,而ES6是基于ES5进行的升级,这是TypeScript是JavaScript超级的理解。
二、为什么要学习TypeScript?
1、TypeScript对比JS
TypeScript属于静态类型的编程语言,JS属于动态类型的编程语言。
- 静态类型:编译期做类型检查
- 动态类型:执行期做类型检查
- 代码先编译后执行(执行编译后的代码)
TS在代码编译的时候(代码执行前)就可以发现错误(早)
JS在代码执行的时候(编译后执行时)才能发现错误(晚)
2、TS相比JS的优势
1.更早(写代码的同时)发现错误,减少找Bug、改Bug时间,提升开发效率。
2.程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。
3.强大的类型系统提升了代码的可维护性,使得重构代码更加容易。
4.支持最新的 ECMAScript语法,优先体验最新的语法,让你走在前端技术的最前沿。
5.TS类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。
三、TypeScript基础知识
1、TypeScript的安装和编译
1.1 通过 typescript 包来编译.ts 文件
安装命令: npm i -g typescript 。
typescript 包:用来编译TS代码的包,提供了tsc 命令,实现了TS-> JS 的转化。
验证是否安装成功:tsc-V (查看 typescript的版本)。
使用方法:
- **将TS代码通过tsc命令编译成JS代码,**在终端中执行命令:
tsc code.ts (此时在同级目录中会出现一个同名的code.js 文件) - 执行JS代码,在终端中执行命令:
node code.js
1.2 通过 ts-node 包来编译.ts 文件
上面的执行命令比较繁琐,提供一个比较简单的命令
**安装命令: **npm i -g ts-node
ts-node 包在内部已经将TS 转化 JS ,然后再运行JS 代码
使用方法:ts-node 文件名称 ,例如:ts-node code.ts
1.3 通过 esno 包来编译.ts 文件
上面的执行命令比较繁琐,提供一个比较简单的命令
**安装命令: **npm i -g esno
esno 内部使用了esbuild 来加载 TypeScript 和 ESM的Node.js运行时
使用方法:esno 文件名称 ,例如:esno code.ts
1.4 通过 ts-node-dev 包来编译.ts 文件【推荐】
上面通过esno 或ts-node 执行命令,如果更改了代码,esno 或ts-node 不支持自动监听ts文件变化,自动重新执行程序,而ts-node-dv 包就可以实现自动监听文件的变化然后自动重新执行最新的程序,节省了重新启动命令的时间,更加方便。
**安装命令: **npm i -g ts-node-dev
运行ts-node-dev命令,还有一个简短的别名命令tsnd ,可以在终端中输入 tsnd 检查下是否安装成功。如果出现如下图所示内容,说明安装成功了。
使用方法:tsnd --respawn 文件名称 ,例如:tsnd --respawn code.ts ,在文件中,更改代码后,会自动执行更改后的代码运行结果。
四、TypeScript中的类型
JS有类型,但是JS不会检查变量的类型是否发生变化,而TS会检查
1、TypeScript基础类型
TypeScript支持与JavaScript几乎相同的数据类型。
以下TS代码,放到一个ts文件中,并通过 tsnd --respawn 文件名称 ,例如:tsnd --respawn code.ts 方式进行执行。
1.1五中常见的基础类型(原始类型)
let name1: string = 'xiaozhu';
let name2: string = `xiaozhu`;
let age: number = 18;
let cool: boolean = true;
let u: undefined = undefined;
let n: null = null;
1.2 数组
let arr1: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
1.3 联合类型
let arr2: (number | string)[] = [1,3,'a','c']
let arr3: (number | string)[] = [1,3]
let arr4: (number | string)[] = ['a','b']
let arr5: number | string[] = ['a','b','3']
let arr6: number | string[] = 123
1.3 元组(Tuple)
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let array7: number[] = [1,2,3,5];
let position1: [ number,number ] = [12, 13]
let position2: [ number,string ] = [12, '13']
let position3: [ number,string ] = ['12', 12]
let position4: [ number,string ] = [12,'13',14]
但使用元组确实能帮助我们进一步提升数组结构的严谨性。
2、TypeScript 特殊类型
2.1 any
any 指的是一个任意类型,如果我们想要为那些在编程时不太清楚的类型变量指定一个类型时,可以使用any类型来标记这些变量。但是也是由于any 提供了这样的“便利性”,我们可以对任何为any 的类型的变量进行任何操作,即使是在获取该类型的变量中没有的属性或者方法,也不会报任何错误,如下代码所示:
let everybody: any = []
everybody.name = '筱竹'
everybody.age = 18
everybody.sayHi()
let age: number = everybody
let name: string = everybody
根据上述的"错误演示",给变量设置any 类型不推荐使用(不推荐,不建议使用any 类型),因为如果在TS代码中很多变量都是any 类型,此时TS的静态类型的检查(静态类型:编译期做类型检查)不起任何的作用(any 会绕过TS的类型检查),跟没有用TS是一样的,所以原则上不推荐使用any 类型。
2.2 void
void 指的是没有任何类型,某种程度上来说,void 类型像是与any 类型相反, void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值,我们可以认为 void 表示一个空类型,如:
function handle(name: string): void {
console.log('您好',name)
}
handle('张三')
2.2 never
never 指的是永远不存在的值的类型, 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;
function error(msg: string): never {
throw new Error(msg)
}
error('张三')
function infiniteLoop(): never {
while (true) {}
}
never 类型可以是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never 的子类型或可以赋值给never 类型(除了never 本身之外)。 即使 any 也不可以赋值给never ,如下代码所示:
let MyAge: never = 18
let MyName: never = 'string'
let MyCool: never = true
let Age: number = MyAge
let Name: string = MyName
let Cool: boolean = MyCool
2.3 unknown
unknown 是 TypeScript 3.0 版本中新添加的一个顶级类型,用来描述类型并不确定的变量, 对照于any ,unknown 是类型安全的(因为any 会绕过TS类型检查,而unknown 会进行类型的检查,所以unknown 是类型安全的,当我们不知道是什么类型的时候,可以用unknown 替代any ), 任何值都可以赋给unknown ,但 unknown 类型的值只能赋值给 unknown 或 any 。
let userName: unknown = '筱竹'
let userAge: unknown = 18
let userBool: unknown = true
let info: unknown
let typeUnknown: unknown = info
let everything: any = info
let num: number = info
2、TypeScript常用类型(一)
2.1 函数类型
函数类型包含两部分:参数类型和返回值类型。
给函数类型,单独指定参数类型和返回值类型,代码如下:
function add(x: number, y: number): number {
return x + y
}
console.log('add', add(1, 2))
let myAdd = (x: number, y: number): number => {
return x + y
}
console.log('myAdd', myAdd(2, 3))
给函数类型,同时指定参数类型和返回值类型,代码如下:
let Add: (x: number, y: number) => number = (x, y) => {
return x + y
}
console.log('Add', Add(3, 4))
2.2 可选参数
在TypeScript里我们可以在参数名后面使用 ? 实现可选参数的功能,可选参数就是 参数是可选的,可传可不传。
function helloName(name1: string, name2?: string) {
console.log(`hello:${name1},${name2}`)
}
helloName('筱竹', 'TS')
helloName('筱竹')
function helloAge(age1?: number, age2?: number) {
console.log(`age:${age1},${age2}`)
}
helloAge(18, 3)
helloAge(18)
注意:必选参数不能位于可选参数后,如下所示:
function helloNum(num1?: number, num2: number) {
console.log(`hello:${num1},${num2}`)
}
helloNum(1, 2)
上面的代码把鼠标移动到num2 参数上是会提示:必选参数不能位于可选参数后,如下图所示:
2.3默认参数
在TypeScript中,我们也可以为参数提供一个默认值,假如用户没有传递这个参数或传递的值是undefined时,就会使用默认参数的值。
function sayHi(name?: string, age = 18) {
console.log(`hi大家好,我是${name},我今年:${age}了`)
}
sayHi('筱竹')
sayHi('筱竹', 20)
2.4对象类型
const obj1: {name: string; age: number; sayHi(): void} = {
name:'张三',
age:10,
sayHllo(){
console.log('hello')
}
}
console.log(obj1.age)
obj1.sayHllo()
const obj2: {
name: string
age: number
sayHllo(): void
} = {
name:'张三',
age:10,
sayHllo(){
console.log('hello')
}
}
console.log(obj2.age)
obj2.sayHllo()
2.5类型别名(Type Alias)
使用类型别名 可以为任意类型指定别名
使用场景:当同一个类型被重复使用多次时,可以使用类型别名来简化该类型的使用。
type typeObj = {
name: string
age: number
sayHllo:() => void
}
const obj3: typeObj = {
name: '张三2',
age: 31,
sayHllo:() =>{
console.log('hello')
}
}
obj3.sayHllo()
type typeArr = (number | string)[]
let arr1: typeArr = [1, 2, '3', 4]
let arr2: typeArr = ['张三', 18, '李四', 14]
console.log('arr1', arr1)
console.log('arr2', arr2)
2.6接口(interface)
因为TypeScript的核心原则之一是对值所具有的结构进行类型检查,那么接口(interface)的作用就为对象中属性的类型命名定义契约(描述对象属性值的类型),同时当一个对象类型被多次重复使用时,使用接口(interface),也能够达到复用的目的。
interface typeObj {
name: string
age: number
sayHllo: () => void
}
const person1: typeObj = {
name: '张三',
age: 19,
sayHllo: () => {
console.log('hello')
},
}
const person2 = <typeObj>{
name: '李四',
age: 18,
sayHllo: () => {
console.log('hi')
},
}
person1.sayHllo()
person2.sayHllo()
let person3 = {
name: '王五',
age: 20,
sayHllo: () => {
console.log('您好')
},
}
let foo = (lableObj: typeObj) => {
console.log(lableObj.age)
lableObj.sayHllo()
}
foo(person3)
注意:
- 传入的对象必须满足接口中的必要的属性,那么它就是被允许的
interface typeObj {
name: string
age: number
sayHllo: () => void
}
let person4 = {
name: '赵六',
sayHllo: () => {
console.log('您好')
},
}
let foo = (lableObj: typeObj) => {
lableObj.sayHllo()
}
foo(person4)
- 类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以
interface typeObj {
name: string
age: number
sayHllo: () => void
}
let person5 = {
age: 21,
sayHllo: () => {
console.log('您好')
},
name: '赵六',
}
let foo = (lableObj: typeObj) => {
lableObj.sayHllo()
}
foo(person5)
类型别名(type)与接口(interface)区别:
**相同点:类型别名(type)和接口(interface)**都可以描述一个对象(给对象指定类型),如
type typeObj = {
name: string
age: number
sayHllo:() => void
}
interface typeObj {
name: string
age: number
sayHllo: () => void
}
不同点:接口(interface) 只能为对象指定类型,而 **类型别名(type)**不仅仅能够为对象指定类型,而且能够为任意类型指定别名。
type Name = string
let Name = '筱竹'
console.log('Name', Name)
type NameAge = string | number
let NameAge = 18
console.log('NameAge', NameAge)
type typeArr = [string, number, boolean]
let Arr: typeArr = ['筱竹', 18, true]
console.log('Arr', Arr)
2.7 可选属性
接口里的属性不全都是必需的,有些是只在某些条件下存在,或者根本不存在。给函数传入的参数对象中只有部分属性赋值了。带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个? 符号。
function myAxios(config: {url: string; method?: string}){
console.log(config)
}
myAxios({url:'https://api.twitter.com'})
interface GetConfig {
url: string;
method?: string;
}
function myAxios(config: GetConfig): { url: string; method?: string } {
let newMethod = {url: "https://www.baidu.com", method: 'Get'};
if (config.url) {
newMethod.url = config.url;
}
if (config.methods) {
newMethod.method = config.method;
}
return newMethod;
}
let mySquare = myAxios({url: "https://www.boxuegu.com"});
2.8 继承接口
能够从一个接口里复制属性或者方法到另一个接口里,可以更灵活地将接口抽离到可重用的模块里,实现复用。
interface infObj1 {
x: number
y: number
}
interface infObj2 extends infObj1 {
z: number
}
const positionObj: infObj2 = {
x: 1,
y: 1,
z: 3,
}
console.log('positionObj.x', positionObj.x)
console.log('positionObj.z', positionObj.z)
interface infObj3 extends infObj1 {
z: number
}
const positionObj1 = <infObj3>{
x: 1,
y: 1,
z: 3,
}
console.log('positionObj1.x', positionObj1.x)
console.log('positionObj1.z', positionObj1.z)
4、TypeScript常用类型(二)
4.1 类型推论
在TS中,有些没有明确指出类型的地方,TS中的类型推论会帮助提供类型,也就是说即使有些地方不写类型注解,TS也会根据类型推论的机制来提示类型。
类型推论可以分为基础的类型推论和上下文推论
基础的类型推论如下:
鼠标放到变量名称上去后的,TS类型推论机制会帮助提示类型,如下图所示:
代码如下所示:
let userName = 'xiaozhu'
let userAge = 3
let userInfo = ['筱竹', 18, true, undefined]
上下文推论
顾名思义,所谓上下文推论,就是TS的类型推论根据变量所在的上下文环境进行推论变量的类型。如下代码所示:
function fn(num1 = 10, num2: number) {
return num1 + num2
}
需要注意的是,虽然TS中有类型推论的机制来帮助我们提供类型,提升开发效率,但并不代表我们在任何场景下都不需要写类型注解,而完全依赖于类型推论的帮助提供类型,而是要分清楚情况,TS的类型机制适用于如下3个场景:
1、变量初始化赋值时
let userName = 'xiaozhu'
2、函数中有默认值的函数参数
function fn(num1 = 10, num2: number) {
return num1 + num2
}
3、函数中函数返回的类型
function fn(num1:number, num2: number) {
return num1 + num2
}
4.2 类型断言
TypeScript的类型检查不一定是最准确的,有时候,我们需要手动的指定值的类型,才能得到我们想要的类型,如下图代码所示:
以下是通过类型检查,显示 divEl 的类型可能是 HTMLElement 或者是 null ,但是我们根据代码更直观的看出,divEl 的类型就是 HTMLElement ,所以这个时候,我们比TS 更清楚一个值的类型,这时候我们就可以使用类型断言来指定更具体的类型了。
根据以上代码,使用类型断言如下所示:
const divEl = document.getElementById('div') as HTMLElement
类型断言使用 as 语法来实现,as 后面的类型是具体的类型。
以上代码,使用类型断言后,通过类型推断得出,divEl 的类型可能是 HTMLElement ,没有了 null 类型。如下图所示:
类型断言除了使用 as 语法以外,也可以使用 <> 语法,如下代码所示:
const divEl = <HTMLElement>document.getElementById('div')
由于TS中的<> 语法表示类型断言,在结合JSX 中的<> 语法,代码解析上有冲突,所以不建议使用<> 这个类型断言语法,更建议使用 as 语法,同时TS在 .tsx 文件里禁用了使用<> 语法的类型断言。
4.3 字面量类型
在TS中的字面量类型,跟我们在JS中的字面量类型理解上还不太一样,如下代码所示:
let num1 = 12
const num2 = 13
我们根据TS中的类型推论,不难看出 num2 的类型是 确实是 13 类型,不是我们直观意义上的 number 类型,为什么13 可以作为类型呢?在TS中,这就叫字面量类型(Literal Types)。
由于 let 声明的变量,变量的值是可以变化的(可以重新赋值),所以类型为 number 类型。
由于 const 声明的变量,变量的值不能变化(被赋值后,不能再改变),只能是 13 类型,所以类型为 13 类型。
在TS中字面量类型主要包括:字符串字面量类型、数字字面量类型、布尔字面量类型和对象字面量类型,它们都可以直接作为类型注解进行使用,如下代码所示:
const str: 'xiaozhu' = 'xiaozhu'
const num: 18 = 18
const bool: true = true
interface ObjInfo {
name: 'xiaozhu'
age: 16
}
const obj: ObjInfo = {
name: 'xiaozhu',
age: 16,
}
在TS中字面量类型相较于TS基础类型(原始类型)更加精确、严谨,因为字面量类型要求变量的值,必须跟字面量完全一致才行,不然就会报错,而TS基础类型,只要是符合基础类型中的任意同类型值就可以,就不会报错。
如下图所示:
在实际应用中,TS中的字面量类型常与联合类型、接口(或类型别名)等一起搭配使用,用来表示一组明确的可选值列表,如下代码所示:
interface Options {
behavior: 'go' | 'jump' | 'run' | 'sit'
sex: '男' | '女'
isShow: true | false
age: 20 | 30 | 40 | 50
say: (() => void) | null
}
const people: Options = {
behavior: 'go',
sex: '女',
isShow: true,
age: 20,
say: null,
}
type Direction = 'up' | 'right' | 'down' | 'left'
function move(config: Direction) {
console.log(config)
}
move('up')
4.4 枚举
枚举(Enum)是TS中新增的一个类型,枚举用来定义一个带名字的常量集合,枚举类型的使用类似于字面量类型+联合类型的组合使用,enum 是枚举的语法。
我们对以下的类型别名代码,改造成枚举类型。
type Direction = 'up' | 'right' | 'down' | 'left'
enum Direction2 {
up,
right,
down,
left
}
function go(config: Direction2) {
console.log(config)
}
go(Direction2.up)
当我们把鼠标移动到枚举常量成员上时,发现枚举常量成员是有值的,枚举第一个成员默认值是 0,剩下成员的值依次累加1,这也就解释了,为什么 在go 函数中,console.log(config) 的值为什是0了。
TS中的枚举主要包括:数字枚举、字符串枚举、异构枚举、常量成员和计算成员、枚举成员类型和联合枚举、常量枚举、外部枚举。
4.4.1 数字枚举
以下代码,我们定义了一个数字枚举 up的值默认从 0开始,其它枚举中的成员会从1开始递增1。
enum Direction2 {
up,
right,
down,
left
}
jump(Direction4.left)
以下代码,我们定义了一个数字枚举, up 使用初始化为 10。 其余的成员会从 10开始自动递增1。
enum Direction3 {
up = 10,
right,
down,
left,
}
function run(option: Direction3) {
console.log(option)
}
以下代码,我们定义了一个数字枚举,给枚举中的成员分别初始化一个数字。
enum Direction4 {
up = 10,
right = 14,
down = 18,
left = 20,
}
function jump(info: Direction4) {
console.log(info)
}
jump(Direction4.left)
4.4.2 字符串枚举
在一个字符串枚举里,枚举中每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
因为字符串枚举成员不能自增,所以需要给每个字符串枚举成员设置初始值。
enum Direction5 {
up = 'UP',
down = 'DOWN',
left = 'LEFT',
right = 'RIGHT',
}
enum Direction6 {
up = Direction5.up,
down = Direction5.down,
left = Direction5.left,
right = Direction5.right,
}
4.4.3 异构枚举
一个枚举中同时包含字符串成员和数字成员,这样的枚举就称之为异构枚举。
enum Direction7 {
name = 'xiaozhu',
age = 18,
}
4.4.4 常量成员和计算成员
以下代码中 Info1 、Info2 中枚举成员都是常量成员
enum Info1 {
num1,
num2,
num3,
}
enum Info2 {
num1 = 1,
num2,
num3,
}
当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
- 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
- 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
- 带括号的常量枚举表达式
- 一元运算符
+ , - , ~ 其中之一应用在了常量枚举表达式 - 常量枚举表达式做为二元运算符
+ , - , * , / , % , << , >> , >>> , & , | , ^ 的操作对象。 若常数枚举表达式求值后为 NaN 或 Infinity ,则会在编译阶段报错。
除了以上情况之外,所有其它情况的枚举成员都是计算成员。
常量成员和计算成员代码如下所示:
enum FileAccess {
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
G = '123'.length,
}
4.4.5 枚举成员类型和联合枚举
4.4.6 常量枚举
常量枚举通过在枚举上使用 const 修饰符来定义。
const enum Direction8 {
up = 'UP',
down = 'DOWN',
left = 'LEFT',
right = 'RIGHT',
}
常量枚举只能使用常量枚举表达式(常量枚举不允许包含计算成员),并且不同于常规的枚举,它们在编译阶段会被删除。常量枚举成员在使用的地方会被内联进来。 因此常量枚举的成员都必须是常量成员,如下代码所示:
const enum Direction9 {
up,
down,
left,
right,
}
let info = [Direction9.up, Direction9.down, Direction9.left, Direction9.right]
通过 tsc code.ts 命令,生成 code.js 文件。TS代码编译成 JS代码后,Direction9 枚举的定义被删除了,info 数组中对 Direction9 的引用也变成了常量值的引用(Direction9.up 内联了 0、Direction9.down 内联了 1、Direction9.left 内联了 2 、Direction9.right 内联了 3),如下代码所示:
var info = [0 , 1 , 2 , 3 ]
4.4.7 外部枚举
在 TS 中,可以通过 declare 关键字描述一个已经存在的枚举类型,通过 declare 定义出来的 枚举类型 叫 外部枚举。
declare enum UserInfo {
name = 'xiaozhu',
age = 18,
}
在TS中,其它类型仅仅被当做类型,而枚举不仅用作类型使用,还可以提供值(枚举成员都是有值的),也就说,在TS中其它的类型被当做类型时,TS代码会在编译为JS代码时,自动把类型删除掉,而枚举类型会被编译为JS代码。
let userName: string = 'xiaozhu'
enum Description10 {
run = 'RUN',
jump = 'JUMP',
go = 'go',
sit = 'SIT',
}
以上代码,通过 tsc code.ts 命令,会编译成如下代码:
var userName = 'xiaozhu';
var Description10;
(function (Description10) {
Description10["run"] = "RUN";
Description10["jump"] = "JUMP";
Description10["go"] = "go";
Description10["sit"] = "SIT";
})(Description10 || (Description10 = {}));
后续TypeScript学习总结(二)高级类型等内容正在编写中,敬请期待。??
|