TypeScript详细解析


管什么真理无穷, 进一寸有一寸的欢喜




一、类型系统

1. 强类型、弱类型(类型安全)

  • 强类型: 在语言层面限制了,函数的实参类型和形参类型必须一致
  • 弱类型: 在语言层面不会限制实参类型
  • 强类型中,不允许任意的隐形类型转换

2. 静态类型、动态类型(类型检查)

  • 静态类型: 一个变量,声明时他的类型就是明确的,声明过后,他的类型就不允许再修改
  • 动态类型: 运行阶段才能明确变量类型,变量的类型随时可以改变
  • 静态类型语言中的变量是没有类型的,变量中存放的值是有类型的

TypeScript详细解析_第1张图片

3. JavaScript 类型系统特征

  • JavaScript 是一门弱类型 / 动态类型的语言
  • 源于早期 JS 语言只是为了解决简单应用
  • JS 是一种脚本语言,没有编译环节,静态类型会在编译时做类型检查

4. 弱类型的问题

  • 只有在运行后,才能发现类型问题
  • 类型不确定会导致执行结果不符合的隐患
  • 传入的参数可能会被自动转换了类型

5. 强类型的优势

  • 错误更早暴露: 编码阶段就可以提示错误
  • 代码更智能,编码更准确
  • 重构更可靠
  • 减少不必要的类型判断



二、Flow 静态类型检查器

1. Flow概述

认识 JS 静态类型检查工具 Flow

// 类型注解
function sum(a: number, b: number) {
  return a + b
}

VScode 配置:
Code => 首选项 => 设置 => 搜索:javascript validate => 关闭 JavaScript 校验
TypeScript详细解析_第2张图片

2. Flow 基础用法

⑴. 安装 flow
npm init -y
// 目录下新增了一个 `package.json` 的文件
npm i flow-bin --dev
// 目录下已经安装好了 flow,并且可以看到依赖包:node_modules、package-lock.json
⑵. 编辑 package.json
  "scripts": {
    ...
    "flow": "flow"
  },
⑶. 初始化flow
npm run flow init
// 目录下会新增一个 `.flowconfig` 文件
⑷. 新建测试文件

目录下新增一个测试 ts 文件:

@flow 标识才能够被 flow 捕获到

// @flow
const sum = (a:number, b:number) => a + b
sum(1, '2')

如果提示 js 语法校验,需要手动关闭(如上图)
设置 -> javascript valida -> 关闭JavaScript验证

⑸. 运行 flow
npm run flow
// => No errors!  (并没有报错,不符合预期)
// 如果并没有报错,那就执行下面的停止 flow,然后再次启动 flow 即可
⑹. 停止 flow
// 关闭 flow
npm run flow stop
⑺. 展示

在这里插入图片描述

3. 编译移除注解

添加了类型注解的文件不能运行,所以需要在编译过程中移除类型注解

⑴. 安装
// 安装 移除插件
npm add flow-remove-types
⑵. 编辑 package.json
  "scripts": {
    ...
    // src 是博主的文件目录,如果直接在当前目录,改为`src/` =>  `./`
    "flowRemove": "flow-remove-types src/ -d dist/"
  },

4. Bable:编译移除注解 - 方案 2

⑴. 安装
// 安装(博主安装失败了—_-)
npm i @babel/core @babel/cli @babel/perset-flow --dev
⑵. 编辑 .babelrc
{
  "presets": ["@babel/preset-flow"]
}
⑶. 编辑 package.json
"scripts": {
	...
    "babel": "babel src/ -d dist/"
}
⑷. 运行 babel
npm run babel

5. Flow 开发工具插件:编译移除注解 - 方案 3

VScode 自带插件Flow language Support
TypeScript详细解析_第3张图片


二、Flow 用法

1. 类型推断

// @flow
function square(n) {
  return n * n;
}
square('100')

不写类型注解,但是通过计算的过程,flow 会类型推断,并提醒
TypeScript详细解析_第4张图片=> 无法执行算术运算,因为字符串[1]不是数字

2. 类型注解

三种类型注解:函数参数、变量名、函数返回值

// @flow

// 函数参数
function square(a: number) {
  return a * a
}

// 变量名
let num:number = 100

// 函数返回值
function foo():number {
  return 100
}

3. 数据类型

⑴. 原始数据类型

JavaScript 的七种原始数据类型: String、Boolean、Number、Null、Undefined、Symbol、以及 ES6 新增的Symbol、BigInt

// @flow

// string
let str: string = 'string' // 字符串

// number
let num: number = Infinity //NaN //100  数字/非数字/无穷大

// boolean
let a: boolean = true // false  true/false

// null
let b: null = null

// undefined
let c: void = undefined

// Symbol
let d: symbol = Symbol()

⑵. 数组数据类型

指定数组内,每一个元素的数据类型

// @flow

// 通过 Array<> 泛型指定内部元素的数据类型
const arr:Array<number> = [1, 2, 3]

// 通过 [] 指定内部元素的数据类型
const arr2: number[] = [2, 3, 4]

// 元组: 固定数组长度,类似数组字面量的方式指定内部的数据类型
const arr3: [string, number, number] = ['1', 2, 3]

⑶. 对象数据类型

指定对象的数据类型,和字面量语法非常类似

// @flow

// 类似字面量的注解方式
const obj1: { name: string; age: number } = {
  name: "zoe",
  age: 18,
}

// 变量名后面添加 `?` 就表示该字段非必须
const obj2: { name: string; age: number; sex?: 0 } = {
  name: "zoe",
  age: 18,
}

// 对后续添加的键值对做类型限制
const obj3: { [string]: number } = {}
obj3.key1 = "sex"
obj3.key2 = 0

⑷. 函数数据类型

函数参数类型、返回值类型

// @flow

// 回调函数参数类型限制, `=>` 后是返回值的类型
function foo(callback: (string, number) => void) {
  callback("string", 100);
}

⑸. 特殊数据类型
// @flow

// 字面量的类型限定, 后面的参数只能是 `foo`
const a: 'foo' = 'foo'

// 联合数据类型, 后面的参数只能是类型之一
const type: 'success' | 'warning' | 'danger' = 'success'

// 或类型, 后面的参数可以是数字/字符串
const b: string | number = 100

// 自定义类型
type stringOrNumber = string | number
const c: stringOrNumber = 'string'

// maybe 类型, 具体的类型之上, 拓展了undefined null 类型
const gender: ?number = undefined
// => const gender: number | undefined | void

⑹. Mixed 和 Any

Mixed: 所有类型的联合类型(string | number | boolean …)
Any: Any 和 Mixed 作用相同,但 Mixed 仍是强类型,而 Any 是弱类型

4. 运行环境 API

// @flow
const element: HTMLElement | null = document.getElementById('app')

不同文件对应的 API 有类型限制,通过右键 HTMLElement 即可查看类型的声明文件
TypeScript详细解析_第5张图片




四、TypeScript 语法规范和使用

1. 概述

  • TypeScript 是基于 JavaScript 基础之上的编程语言,超集/扩展集(superset)
  • 扩展的主要是更为强大的类型系统,以及对 ECMAScript 的新特性支持
  • 已经成为前端的第二门语言

2. 基础配置

⑴. 安装 TypeScript
// 全局安装
npm install -g typescript
// 查看版本
npm tsc --v

示例:

const hello = (name: string) => {
  console.log(`Hello, ${name}`)
}
// hello(100)
hello('TypeScript')

⑵. 配置文件

编译项目,生成一个配置文件tsconfig.json

npm tsc --init

tsconfig.json配置文件注解

{
  "compilerOptions": {
    // 设置编译后的javascript采用的ECMAScript标准
    "target": "es5", 
	...
    // 输出的代码使用什么方式进行模块化,这里用的是commonJS,会把输入输出弄成require和module.export的方式
    "module": "commonjs",
   
    // 设置编译结果输出的文件夹
    "outDir": "dist",
    /* Redirect output structure to the directory. */

    // 源代码ts文件所在的文件夹
    "rootDir": "src",
    /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */

    /*类型检查相关 Strict Type-Checking Options */
    // 开启严格模式,对类型检查十分严格
    // 例如:any类型,也要严格写出来
    "strict": true,
    /* Enable all strict type-checking options. */
}

⑶. 原始类型
// 在非严格模式下,  类型 下可以是为 null

// string
// let a: string = null
let a: string = "string"

// boolean
let b: boolean = false // true

// number
let c: number = 100 // NaN infinity

// null
let d: null = null

// undefined
let e: void = undefined
let f: undefined = undefined

// Symbol  这里的 Symbol 会报错
let g:symbol = Symbol()

⑷. 标准库声明

内置对象所引用的声明文件

TypeScript详细解析_第6张图片上节中看到的 Symbol 会报错,给出的提示是需要更高版本的声明文件,而 Symbol 是 ES6 新增的数据类型,右键查看定义

TypeScript详细解析_第7张图片这里博主用的是,ES2019 版本(左上角文件名称),所以包含了 Symbol 的类型声明,就不会报错了

解决 Symbol 报错的方法:

  • 需要将配置文件中的target改为es2015
  • 不改target,将lib选项改为[“ES2015”]
  • 其他文件的,console的定义会报错,默认引用的DOM类库被覆盖,在配置文件中加上"DOM",这里的DOM是包含了DOM+BOM

⑸. TS 的中文报错设置

首选项 => 设置 => 搜索 TypeScript locale => 设置 zh-CN
(不过不建议这样使用,因为不利于报错的检索)
TypeScript详细解析_第8张图片

⑹. 作用域问题

当在文件中,定义了其他文件的重名变量时
TypeScript详细解析_第9张图片
解决作用域问题:

const a: number = 100;

// 1. 立即执行函数,将变量改为局部变量
(function () {
  const a: number = 100
})()

// 2. 将文件导出,作为 模块(模块具有独立的模块作用域)
export { }

3. 数据类型

⑴. Object类型

TypeScript 的 Object 类型并不单指对象类型,而是泛指除了原始类型的其他类型

// object 类型   能够接受 数组/对象/函数  等非原始类型
const foo: object = function() {} // {} // []

// 对象类型  需要用到类似字面量的语法
const obj: {name: string, age: number} = { name: 'zoe', age: 18}

⑵. 数组类型
// Array<> 泛型
const arr1: Array<number> = [1, 2, 3]

// 元素类型[]
const arr2: string[] = ['1', '2', '3']

⑶. 元组类型

明确元素数量,以及每一个元素类型的数组

// 元组 [Tuple]

// 限定每一个元素的数据类型
const tuple:[string, number] = ['zoe', 18]

// 依然可以通过数组下标的方式进行访问
const name = tuple[0]
const age = tuple[1]

// 也可以通过数组结构的方式提取
const [name2, age2] = tuple

export { } // 确保其他作用域没有成员冲突

⑷. 枚举类型
  • 给一组数值分别起一个更好理解的名字
  • 一个枚举中只会存在几个固定的值
  • 枚举在编译后,不会被移除,会被编译为键值对对象(常量枚举 enum 前加入 const 可以解决)
export { } // 确保其他作用域没有成员冲突

// 传统方式
const postStatus = {
  unPublish: 0,
  published: 1
}

// 枚举
// 如果不指定值, 默认会从 第一项的值 开始累加(第一项不设值时默认为 0 )
// 如果是字符串则无累加功能
enum postStatus2 {
  unPublish = 0,
  published = 1
}

// 常量枚举,在编译后,枚举会被移除,设定的值会改为数字,枚举的属性名会以注释作为提示
const enum postStatus3 {
  unPublish = 0,
  published = 1
}

const post = {
  title: 'this is title',
  content: 'this is content text',
  // status: 0 // 0 未发布 1 已发布
  // status: postStatus.unPublish
  status: postStatus2.unPublish
}

⑸. 函数类型
export { } // 确保其他作用域没有成员冲突

//  1. 函数声明的方式
// 函数的类型约定: 传入的参数, 返回的结果

// 可以在参数后面添加 ? , 实参内就是非必填(可选参数要在必选参数后面)
// 或者给参数设置默认参数, 也可以非必填
function fn1(a: number, b: string, c?: number, d:number = 10): boolean {
  return true
}

fn1(100, 'str')


// 传递任意个参数
function fn2(a: number, ...rest:number[]): string {
  return 'str'
}


// 2. 函数表达式的方式
const fn3 = function(a: string, b: number):string {
  return 'str'
}

// 如果这个函数作为回调函数的参数, 那这个函数也是需要约定类型的
const fn4: (a: string, b: number) => string = function(a: string, b: number):string {
  return 'str'
}

⑹. 任意类型
  • any 是一种弱类型/动态类型
  • ts 不会对 any 类型检查,所以慎用
export { } // 确保其他作用域没有成员冲突

// 这里的 value 需要支持任意类型
function stringify(value: any) {
  return JSON.stringify(value)
}

⑺. 隐式类型推断

在声明变量时,ts 对更具变量的值推断类型,如果未赋值,则为 any
TypeScript详细解析_第10张图片

⑻. 类型断言
export{}

const arr = [111, 222, 333, 444, 555]

const result = arr.find(i => i > 2)

// ts 检测推断 result 的类型为 number | null,所以下面的类型会提醒
const square = result * result

// 类型断言 1. as 方式
const res = result as number

// 类型断言 2. <> 方式
// <> 会和 jsx 的标签形成语法冲突
const res2 = <number>result

⑼. 接口
①. 基本使用

约束对象的结构

export { }

// 因为使用了 post 内的数据, 所以需要对 post 进行数据约定
function printPost(post: Post) {
  console.log(post.title)
  console.log(post.content)
  console.log(post.status)
}

// 接口
interface Post {
  title: string
  content: string
  status: number
}

②. 补充
export { }

// 接口
interface Post {
  title: string
  subTitle?: string // ? 为可选 => 增加了 undefined 类型
  readonly content: string // readonly 为只读成员 => 之后重新赋值会报错
  status: number
}

const article: Post = {
  title: 'title',
  content: 'content',
  status: 0,
}

// article.content = 'content 2'


// 动态数据 约定类型
interface Cache {
  // key 为键, []: 后的为值类型
  [key: string] : number
}

// 添加动态成员
const arr: Cache = {
  'weight': 11,
  'height': 50
}

3. 类

⑴. 基本使用

描述具体对象的抽象特征

export { }

class person {
  // 需要在 class 中添加 person 的属性, 而不能直接在 构造函数(constructor) 中直接添加
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = 100
  }

  // 声明方法  通过类型注解的方式来约定 参数 和 返回值
  sayHi(msg: string):void {
    console.log(`Hi, my name is ${this.name}, {msg}`)
  }
}

⑵. 访问修饰符
export { }

class person {
  name: string
  private age: number // 私有属性
  public sex?: number // 公有属性(默认)
  protected gender: boolean // 受保护的属性, 只允许在子类中访问父类中的成员

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
    this.gender = true
  }

  sayHi(msg: string): void {
    console.log(`Hi, this is ${this.name}, ${msg}`)
    console.log(this.age)
  }
}

const tom = new person('tom', 42)
console.log(tom.name)
// console.log(tom.age)
// console.log(tom.gender)

class Students extends person {
  // 将构造函数设置为了 私有属性, 外部无法访问, 可以通过内部构造后导出
  private constructor(name: string, age: number) {
    super(name, age)
    console.log(name)
    console.log(this.sex)
    console.log(this.gender)
  }

  // 创建一个静态方法 
  static creat (name: string, age: number) {
    // 返回一个实例方法
    return new Students(name, age)
  }
}

// 通过子类导出 外部就可以获取 Students 的实例对象了
const aim = Students.creat('zoe', 18)

⑶. 类与接口
export { }

// 不同的类型,相同的接口
// interface eatAndRun{
//   // 函数签名的方式约束
//   eat(food: string):void
//   run(distance: string):void
// }


// 抽象的方式解耦接口
interface Eat{
  eat(food: string):void
}
interface Run{
  run(distance: string):void
}

// 实现对应的接口
// class Person implements eatAndRun {
class Person implements Eat, Run {
  eat(food: string): void {
    console.log(`优雅的进餐:${food}`)
  }
  run(distance: string) {
    console.log(`直立行走:${distance}`)
  }
}

// class Animal implements eatAndRun {
class Animal implements Eat, Run {
  eat(food: string): void {
    console.log(`咕噜咕噜的吃:${food}`)
  }
  run(distance: string) {
    console.log(`爬行:${distance}`)
  }
}

⑷. 抽象类
export { }

// 类型被定义 为抽象之后, 只能被继承, 不能通过 new 来创建对象实例了
abstract class Animal {
  eat(food: string): void {
    console.log(`咕噜咕噜吃东西${food}`)
  }

  // 抽象的类, 只能被定义抽象的方法(抽象方法可以不需要方法体)
  abstract run(distance: number): void
}

// 继承 Animal 的类
class Dog extends Animal {
  run(distance: number): void {
    console.log(`爬行${distance}`)
  }
}

// 创建子类的实例对象, 就包含父类的实例方法 和 自身的实例方法
const d = new Dog()
d.eat('热干面')
d.run(100)

⑸. 泛型

定义时不能够明确的类型,定义为参数,在使用的时候再对参数进行定义

export { }

// 创建一个指定数组长度的数组
function creatNumberArray(length: number, value: number): number[] {
  // 泛型参数 <>
  const arr = Array<number>(length).fill(value) // fill 为填充满数组
  return arr
}

// 如果要创建一个 string 的数组(硬方法, 和上方法有重复编码)
function creatStringArray(length: number, value: string): string[] {
  const arr = Array<string>(length).fill(value)
  return arr
}

const res = creatNumberArray(3, 100)
// => [100, 100, 100]


// 泛型<> :  T 作为未定义的类型
function creatArray<T>(length: number, value: T): T[] {
  const arr = Array<T>(length).fill(value)
  return arr
}

// 在使用的时候再约定数据类型
const res2 = creatArray<string>(3, 'string')

4. 类型声明- lodash

安装 ts 的类型声明模块

// 添加 lodash
npm add lodash
// 安装 ts 声明模块
npm add @types/lodash

// 安装 query-string
npm add query-string

示例:

export { }

// 将字符串转换成 驼峰格式
import { camelCase } from 'lodash'

// 类型声明  (在使用时,单独为类型做使用声明)
// declare function camelCase(input: string): string

// 参数应该是 string , 返回值也是 string(ts 没有类型推断)
const res = camelCase('hello world')


import qs, { parse } from 'query-string'

// 解析 url 中的 query-string 字符串(包含类型声明)
qs.parse('?key=value&key2=value')




下一篇:JavaScript 性能优化

你可能感兴趣的:(LaGo,前端)