TypeScript 学习

153 阅读10分钟

我正在参加「掘金·启航计划」

TypeScript 导言

TypeScript4 中文文档

mqyqingfeng.gitee.io/learn-types…

TS 是什么及其优点

Typescript 是一种由微软开发的自由开源的编程语言,它是 js 的一个超集,扩展了 js 的语法,主要提供了类型系统和对 ES6 的支持。Angular2 与 Vue3 即是 TS 开发的。

其优势为强大的 IDE 支持:

1. 类型检查:在 TS 中允许为变量自定义类型。

2. 语法提示。

3. 重构。

使用 TS 的优点:

1.类型检测:在 Typescript 中为变量指定具体类型时,IDE会做出类型检测,这个特性减少在开发阶段犯错几率。

2.语法提示:在IDE里编写 Typescript 代码时,IDE 会根据你当前的上下文,把你能用的类、变量、方法和关键字都给你提示出来。直接选择,这个特性提高开发效率。

3.便于重构:重构是说你可以很方便的去修改你的变量或者方法的名字或者是文件的名字,当你做出这些修改的时候,IDE 会帮你自动引用这个变量或者调用这个方法地方的代码自动帮你修改掉。

4.活跃社区: Typescript 拥抱 es6 的规范,也支持部分 ESNext 草案规范,大部分的第三方库提供 Typescript 类型定义的文件。

TypeScript 的最大特点是静态类型,不同于 javascript 的动态类型,静态类型有以下优势∶

  1. 其一,静态类型检查可以做到 early fail ,即你编写的代码即使没有被执行到,一旦你编写代码时发生类型不匹配,语言在编译阶段(解释执行也一样,可以在运行前)即可发现。
  2. 其二,静态类型对阅读代码是友好的,针对大型应用,方法众多,调用关系复杂,不可能每个函数都有人编写细致的文档,所以静态类型就是非常重要的提示和约束。此外 TS 还实现了类,接口,枚举,泛型,方法重载等语法糖,方便了前端开发。

学习 Ts 的前提

学习 Ts 需要理解接口(interfaces)、泛型(Generics)、类(classes)、枚举类型(Enums)等前端开发不熟的知识点。

  • 接口(Interfaces):可以用于对[对象的形状(Shape)]进行描述。
  • 泛型(Generics):是指在定义函数、接口或类的事或,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
  • 类(Class):定义了一件事物的抽象特点。

一、 TypeScript 简单了解

1、 Ts 环境安装

全局安装好 Ts 准备开始学习。因为其浏览器本身并不能解析 ts,所以需要在编写好 ts 时,需要使用命令将 ts 编译为 js 文件。

  1. 安装:

npm i -g typescript

  1. 查看版本

tsc -v

  1. 编译 ts 文件

tsc index.ts

2、 Ts 简单使用

刚了解到 ts 时,给我的感觉就是 ts 让 js 变得和 java 一样成为了“强类型”语言,因为其在声明变量时就得规定其定义的数据类型。

例:

  1. 定义一个 类型为 number,值为 15 的变量 num
// num(变量名) :number(变量类型) = 15(具体值)
let num:number = 15
  1. 定义一个 类型为 string,值为 'abc' 的变量 str
// str(变量名) :string(变量类型) = 'abc'(具体值)
let str:string = 'abc'
  1. 这里再看一个示例:

因为以前的 js 并没有强规定传的参数类型,所以在调用函数时,所传的参数并没有限制,也就导致了我们在团队开发时调用别人的函数时,可能会因其不熟悉其函数,而传错了参数导致的意外 Bug。

而且 js 的函数在调用时并不会报错,而是会在运行后抛出错误,这可能就导致了我们代码出 Bug 时我们却不知道 Bug在哪的问题。

但是 ts 则不同,ts 在传参的同时便会检索传入函数的参数内容是否符合规范,如若不符合便会直接报错。

// 计算两个参数的和
function sum(){
  return x + y
}
// 调用时并不会报错,但是在运行后会报错
sum(true, 3)

// ts 中可以提前定义传进来的参数必须是 number,可以有效防止使用时传其他类型的数据进来
function sum(x: number, y: number){
    return x + y
}
// 正确,不会报错
sum(1, 2)
// 报错
sum(true, 3)

二、 Ts 中定义基础类型

1. ts 中定义变量

1. 指定数据类型定义

指定类型的方式有三种:单类型定义、联合定义、任意类型定义

// 单类型定义
var id: number
// 正确,不报错
id = 2
// 报错,类型定义错误
id = true

// 联合类型定义
// 定义这个 id 可以是字符串也可以是 number
var id: string | number
// 类型不符合,报错
id = true 

// 任意类型定义,表示可以定义任何类型的数据
var s: any
// 这样就和 js 中定义变量是一致的,什么类型都可以定义并改变
// 不报错
s = true

2. 枚举方式定义

给定变量能够的数据某个范围,如果变量没有使用指定范围中的数据,就会报错。

let gender: "male" | "female"
gender = "male"
// 会报错
gender = "boy"

let dice: 1 | 2 | 3 | 4
dice = 1
// 会报错
dice = 5

2. ts 定义数组

ts 中定义数组的方式有两种,一种较为简单,类似于变量,但数组不能使用联合类型。

1. 简单定义

// 定义数组只能添加 number 类型
let arr: number[] = [1, 2, 3]

// 不能使用联合类型定义
let arr1: string | number = [1, 2, '3'] // 报错

// 使用 any 任意类型
let arr2: any[] = [1, 2, '3']

2. 使用泛型(Generics)

泛型是指在定义函数、接口或类的数据时,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

语法:

: T 表示变量,可以是任何类型

let Array: Array = []

// 定义 arr3 数组的参数可以是任意的 number 和 string 类型数据
let arr3: Array<number | string> = [1, 2, 3, 'a']

3. 元组定义(Tuple)

也可以单独指定数组中的每项元素的数据类型。

// 类型后面接问号代表可选数据,可以定义也可以不定义
let point: [number, number, number?]
point = [1, 2, 3]
point = [1, 2]

多维数组定义方式。

let arrOfArray: number[][]
arrOfArray = [
	[1, 2, 3],
  [4, 5, 6]
]

3. ts 中定义对象

先看看 js 中对象的定义。

let obj = {
  // 对象中任意定义数据没有任何限制
	name: '你好',
	age: 17,
	qq: '3068495230'
}

1. 接口(interface)

接口是指对对象的形状(Shape)的描述。

interface Iperson {
    // ? 代表可选属性:声明在定义对象时这个属性是否定义都行
    name?: string;
  	// 没加 ? 则代表 age 和 sex 在使用时,必须被定义,或则就会报错
    age: number;
    sex: string;
    // 任意属性:声明在定义的时候,可以任意定义属性
    // [任意名字: 名称类型]: 定义的数据类型
    [propName: string]: any;
}

// 可以简单理解为受到接口的约束
// 有点像定义变量时的类型定义,不过这里是自己定义受到什么约束,
// 只能定义什么类型的数据
let tom: Iperson = {
    // 这里不定义 name 也不会报错
    name: 'tom',
    // 但如果不定义 age 和 sex 就会报错
    age: 18,
    sex: 'man',
  	// obj 对象与 action 方法不定义也不会报错
    obj: {di: 1},
    action: function(){
        console.log(this.age)
    }
}

2. 类型别名(type)

type 的使用方法和 interface 一致,但也是有区别的:

  1. type:
    • 能够直接定义函数类型,却不能在对象中定义函数。
    • 类型别名通过 & 扩展。
    • 类型别名可以为基本类型、联合类型或元组类型定义别名。
  1. interface:
    • 能够在对象中定义函数。
    • 接口通过 extends 来扩展。
    • 同名接口会自动合并,而类型别名不会。
interface type {
    name?: string;
    age: number;
    sex: string;
    [propName: string]: any;
}

let tom: Iperson = {
    // 这里不定义 name 也不会报错
    name: 'tom',
    // 但如果不定义 age 和 sex 就会报错
    age: 18,
    sex: 'man',
  	// obj 对象与 action 方法不定义也不会报错
    obj: {di: 1},
    action: function(){
        console.log(this.age)
    }
}

4. 枚举类型(enum)

枚举是 ts 独有的一种变量类型,js 中并没有。

enum Color {Red, Green, Blue}

三、 Ts 中定义函数

函数的定义方式还挺多的,稍微有点绕。

1. 函数定义

函数类型定义主要分为:

    1. 输入值类型
    2. 返回值类型
// f() 函数规定传入的 x、y 必须为数字,且返回结果也必须是数字
function f(x: number, y:number):number{
    return x + y
}
// 输出:3
f(1, 2)

// 返回值类型可以使用联合类型也可以使用 any
function f1(x: number, y:number):any{
    return x + y
}
// 输出:3
f1(1, 2)

// 如果函数没有返回值可以使用 void 表示
function f2(x: number, y:number):void{
    console.log(x, y)
}
// 输出:1 2
f2(1, 2)

2. 参数默认值定义

默认值:

    • 如果在调用函数时没有传值,则会使用默认值。
// 如果在调用函数时没有传值,则会使用默认值。
function f4(x: number, y :number = 1):number{
    return x + y
}
// 输出:2
f4(1)

3. 可选参数定义

与对象的定义一样,再定义形参时加上 ? 表示这个参数为可选参数,在使用时可以不传入。

// ? 表示可选参数,在使用时可以不传入,前提是内部函数执行不会出问题
function f5(x: number, y :number, z ? :number):number{
  	console.log(z)
    return x + y
}
// 输出:3 3
f1(1, 2, 3)
// 输出:3
f1(1, 2)
// 输出:报错,y 未添加数据
f1(1)

4. 使用泛型定义函数

函数也可以使用泛型进行定义,在调用函数时才进行值的类型定义。

// 使用泛型定义函数
function f6<T>(x: T, y: T):T[]{
    return [x, y]
}
// 传值时定义类型
f6<number>(2, 3)
f6<string | number>('a', 3)
f6<any>(true, null)

5. 使用接口定义函数

interface Sum {
  (x: number, y: number): number
}

// 但是有个弊端,只有以变量的形式定义函数时才能使用接口定义函数
const f: Sum = (x, y) => {
  return x + y
}
// 输出:3
f(1, 2)

6. 小实例

仔细想这样定义有没有问题?

function f7(x: string | number ): number{
    // 因为字符串有长度,而数字没有,所以会报错
    return x.length
}
// 报错
f7('abc')

// 正确方式:
function f8(x: string | number[] ): number{
    // 字符串有长度,而数组也有长度,所以不会报错
    return x.length
}
f7('abc')

四、Ts 中定义 Class

s. 使用接口的方式定义

// 定义
interface UserInfo {
    id: number;
    name: string;
    sex: '男' | '女' | '未知';
    getUserInfo(): {};
    updateName(newName: string): string;
}

// implements
class Dog implements Animal{
    id = 1;
    name = 'achens';
    sex = '男';
    getUserInfo(){
        return {id, name, sex}
    }
    updateName(newName: string){
        return this.name = newName
    }
}

四、Ts 默认配置文件

目录下新建 tsconfig.json 进行配置。

{
	"compilerOptions": {
    // 是否在编译后删除所有注释
    "removeComments": true,
    // 指定 ts 编译文件后的 es 版本
    "target": "es5",
    // 不允许隐式的 any 类型
    "noImplicitAny": true,
    // 不允许把 null 和 undefined 赋值给其它变量
    "strictNullChecks": true,
    // 生成代码的模板标准
    "module": "commonjs",
    // 在代码头部写入 'use strict'
    "alwaysStrict": true
  }
}

参考配置

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictFunctionTypes": false,
    "jsx": "preserve",
    "baseUrl": ".",
    "allowJs": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "experimentalDecorators": true,
    "lib": ["dom", "esnext"],
    "types": ["vite/client"],
    "typeRoots": ["./node_modules/@types/", "./types"],
    "noImplicitAny": false,
    "skipLibCheck": true,
    "paths": {
      "/@/*": ["src/*"],
      "/#/*": ["types/*"]
    }
  },
  "include": [
    "tests/**/*.ts",
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "types/**/*.d.ts",
    "types/**/*.ts",
    "build/**/*.ts",
    "build/**/*.d.ts",
    "mock/**/*.ts",
    "vite.config.ts",
    "src/settings/dist/theme.js",
    "src/tools.js"
  ],
  "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"]
}