邂逅TypeScript

114 阅读3分钟

26-1

邂逅TypeScrpt

官网:typescriptlang.org

TypeScrpt是拥有类型的JavaScript超级,它可以编辑成普通、干净、完整的JavaScript代码 怎么理解上面的话呢?

  • 我们可以将TypeScript理解成加强版的JavaScript
  • JavaScript拥有的特性,TypeScript全部都支持,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是至此的
  • 并且在语言层面上,不仅仅增加了类型约束,而且扩展了一些语法,比如枚举类型(Enum)、元组类型(Tuple)等
  • TypeScript在实现新特性的同时,总是保持和ES标准同步甚至是领先
  • 并且TypeScript最终会被编译成JavaScript代码,所以你不需要担心它的兼容性问题,在编译时也不需要借助于Babel这样的工具 所以我们可以把TypeScript理解为更加强大的JavaScript,不仅让JavaScrpt更加安全,而且给它带来了诸多好用的特性

TypeScript的编译环境

TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:

npm install typescript -g

安装后,可以使用下面命令来查看tscTypeScript compiler)的版本,它可以把TypeScript转换为JavaScipt

tsc --version

我们在.ts的文件中编写TypeScript代码,但是编写完后我们需要在终端中输入

tsc 对应的文件名

可以把TypeScript代码转换为JS代码,生成一个js文件

ts文件

let message:string='hello typescript'

function foo(payload:string){
  console.log(payload);
}

foo('aaa')

export{}

输入命令生成的js文件

"use strict";
exports.__esModule = true;
var message = 'hello typescript';
function foo(payload) {
    console.log(payload);
}
foo('aaa');

为什么需要在ts中写export{}

因为在同一目录下,ts代码都是在同一个作用域的,这样容易造成命名冲突,当我们使用export{}后,当前的ts文件就是独立的作用域,就不会造成命名冲突了

在node中运行TS

如果我们不想每次都使用命令来转换,而是直接运行,有什么好办法么?

安装ts-node可以解决这个问题

npm install ts-node -g

ts-node还依赖了tslib@types/node这两个包,使用我们需要安装它们

npm install tslib @types/node -g

安装后,直接使用下面命令就可以直接运行ts代码了

ts-node xxx文件

webpack打包TS

26-1-45

使用webpack也是可以解决上面的问题的,那我们改怎么做呢?

  1. 先创建包管理文件

    npm init
    
  2. 安装webpack,因为webpack需要使用webpack-cli,所以也需要安装

    npm install webpack webpack-cli -D
    
  3. 创建webpack.config.js进行配置

    安装ts对应的loader

    // 虽然我们全局安装了ts 但是我们需要局部打包 所以我们还需要局部安装ts
    npm install ts-loader typescript -D
    

    使用下面命令创建tsconfig.json文件,没有这个文件是打包不了ts文件的

    tsc --init
    

    安装webpack-dev-sever,解决我们每次修改都需要重新打包的问题

    npm install webpack-dev-server -D
    

    index.html作为模板,需要安装html-webpack-plugin

    npm install html-webpack-plugin -D
    

    配置webpack.config.js

    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      // 模式
      mode: 'development',
    
      // 打包入口
      entry: './src/main.ts',
    
      // 出口
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      },
    
      // 配置查找的后缀名
      resolve: {
        // 我们在package.json中配置了sever 但是dev-server是使用到了js 所以我们还需要配置js的后缀名 不然会出错
        extensions: ['.ts', '.js', '.cjs', '.json']
      },
    
      // 配置loader与plugin
      module: {
        rules: [
          {
            test: /\.ts$/,
            loader: 'ts-loader'
          }
        ],
      },
    
      // 配置plugin
      plugins: [
        new HtmlWebpackPlugin({
          // 使用的模板
          template: "./index.html"
        })
      ]
    }
    

    package.json中配置buildserve

    build在安装webpack后就可以使用了

    serve在安装webapck-dev-server就可以使用了,如果还想要进行本地配置,可以在webpack.config.js中配置devServe

    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "build": "webpack",
      "serve": "webpack serve"
    }
    
  4. 配置完后就可以使用下面命令来运行了

    npm run serve
    npm run build 
    

TypeScript数据类型

细节

类型注解(type annotation:的后面应该为小写的stringTypeScript中的字符串类型,大写的StringJavaScript的字符串包装类的类型

const message: String = 'hello world'
const message: string = 'hello world'

类型推导:当我们没有给变量添加类型注解的时候,TS会对我们的代码进行类型推导自动定义类型

一般情况下,如果可以推导出类型注解时,可以不加类型注解

let message = 'hello world'
message = 123 // 报错

number类型

数字类型是我们开发走经常使用的类型,TSJS语言,不区分整数类型(int)与浮点型(double),统一为number类型

ES6JS增加了二进制和八进制的表示方法,而TS也是支持二进制、八进制、十六进制的

let num: number = 123

// 十进制
let num1: number = 100
// 二进制
let num2: number = 0b111
// 八进制
let num3: number = 0o456
// 16进制
let num4: number = 0x123abcdef

console.log(num1, num2, num3, num4);

bool类型

let flag: boolean = true
flag = false
flag = 20 > 30
console.log(flag);  // false

string类型

let message1: string = 'hello world'

// 默认情况下,如果可以推导出类型注解时,可以不加类型注解
const name = 'zs'
const age = 18

let message2 = `${name}: ${age}岁`
console.log(message2);

export { }

array类型

在数组中存放不同的类型是不好的习惯,所以在TS中可以使用类型注解使数组中的值只能为一种类型

与上面类型注解不一样的是,数组类型是大写的Array,而且定义数组中的值为那种类型需要使用到**泛型(<>)**或者使用:类型[]

// 第一种写法 不推荐 因为与jsx是有冲突的 jsx也是使用尖括号 <div></div>
const names1: Array<string> = []
// 第二种写法 推荐
const names2: string[] = []

names1.push('abc')
names2.push('cba')
names1.push(123) 报错

object类型

// TS会帮我们推导出来
const info1 = {
  name: 'zs',
  age: 18
}

// 如果我们使用类型注解 对象.xxx是拿不到数据的,会报错(编译通不过)
const info2: object = {
  name: 'ls',
  age: 20
}

console.log(info1.name);
console.log(info2.name);  // 报错

null与undefined类型

null类型只有一个值null

undefined也只有只有一个值undefined

如果我们不给null类型注解,那么就是一个any类型,所以当我们是真的需要使用null的时候,加上null类型注解比较好

const n1: null = null
// n2为any类型
const n2 = null	

let n3: undefined = undefined

symbol类型

const title1 = Symbol('title')
const title2 = Symbol('title')

const info = {
  [title1]: '程序员',
  [title2]: '老师',
}

export { }

any类型

在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们就可以使用any类型(类似于Dart语言中的dynamic类型)

any类型有点像一种讨巧的TS手段

  • 我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法
  • 我们给一个any类型的变量赋值任何的值,比如数字、字符串的值
let message: any = 'Hello World'

message = 123
message = true
message = {}
console.log(message);

export { }

如果我们对于某些情况的处理过于繁琐,不希望添加规定的类型注解,或者在引入第三方库时,缺失了类型注解,这个时候我们可以使用any

如果我们实在是不相使用any,可以在tsconfig.js或者tslist里面配置

unknown类型

unknown类型只能赋值给anyunknown类型

any类型可以赋值给任意类型

function foo() {
  return 'abc'
}

function bar() {
  return 123
}

let flag = true
// 可以使用联合类型 string | number 但是我们有时候可能不知道函数返回的是什么类型
// 也可以使用 any 类型
// 但是推荐使用 unknown,防止我们拿到any类型到其他地方乱用
let result: unknown
if (flag) {
  result = foo()
} else {
  result = bar()
}

// 如果result为any类型 不会报错
let message: string = result // 报错
let num: number = result	// 报错

console.log(result);

void类型

如果函数没有返回值的时候,TS会给我们推断出这个函数的类型是void类型

void类型是可以return null

function sum(num1: number, num2: number) {
  console.log(num1 + num2);
  return null
}

sum(20, 30)

// 相当于
function sum(num1: number, num2: number): void {
  console.log(num1 + num2);
}

never类型

never表示永远不会返回值的类型,比如:

  • 如果一个函数走是一个死循环或者抛出一个异常,那么这个函数会返回东西么?
  • 不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型
function foo(): never {
  // 死循环
  while (true) { }
}

function bar(): never {
  throw new Error()
}

应用场景:27-1

tuple类型

tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift

// 如果我们想让 zs 18 1.88 在数组中存储 那么我们只能使用any类型和元组类型
// 当然也可以使用对象

// 但是这样做是有缺点的,因为我们获得到的数据也是一个any类型
const info: any[] = ['zs', 18, 1.88]
const name = info[0]  // any
const age = info[1]
console.log(name.length);
console.log(age.length);  // undefined

// 那我们有什么办法使数组中的数据也是有自己的类型呢? 元组类型可以做到
// 每一个类型对应的需要赋一个值 不然报错
const info2: [string, number, number] = ['zs', 18, 1.88]
const name2 = info2[0]  // string
const age2 = info2[1]
console.log(name2.length);
console.log(age2.length); // 报错

export { }

应用场景:27-1-20

函数类型

()=>void可以返回任何东西

// 1.函数参数类型
function foo() { }

// 函数类型定义的格式如下
type FooFnType = () => void

function bar(fn: FooFnType) {
  fn()
}

bar(foo)

// 2.定义常量时,ts会推导出函数类型注解,当然我们也可以自己定义
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
  return a1 + a2
}

案例

function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
  return fn(n1, n2)
}

const result1 = calc(20, 30, function (a1, a2) {
  return a1 + a2
})
console.log(result1);

const result2 = calc(20, 30, function (a1, a2) {
  return a1 * a2
})
console.log(result2);

函数参数与返回值类型

// 在开发中我们可以不写返回值类型,TS会自动推导
function sum(num1: number, num2: number): number {
  return num1 + num2
}

匿名函数参数类型

在下面中,我们并没有指定item的类型,但是item是一个string类型

  • TS会根据forEach函数的类型以及数组的类型推断出item的类型
  • 这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型
// 通常情况下,在定义一个函数时,都会给参数加上类型注解
function foo(message: string) {

}

const names = ['abc', 'cba', 'nba']

// item根据上下文环境推导出来的,这个时候可以不添加类型注解
names.forEach(function (item) {
  console.log(item.split(""));
})

可选类型

可选类型类似于这个参数是 类型 | undefined的联合类型

如果有必选类型,那么可选类型必须放到必选类型后面

一般的顺序:必传参数 - 有默认值参数 - 可选参数

function foo(x: number, y: number = 30, z?: number) {
  console.log(x, y, z);
}

// undefined只有undefined类型能赋值,所以这里是使用默认参数30
foo(20)
foo(20, undefined)
foo(20, undefined, 40)

参数的对象类型

function printPoint(point: { x: number, y: number, z?: number }) {
  console.log(point.x);
  console.log(point.y);
  console.log(point.z); // 如果没有传 输出undefined
}

printPoint({ x: 123, y: 321 })
printPoint({ x: 123, y: 321, z: 111 })

函数重载

ts中实现函数重载,需要定义重载函数和具体实现函数,例子如下:

// 函数重载:函数的名称相同,但是参数不同的几个函数,就是函数的重载
// 定义函数重载
function add(num1: number, num2: number): number
function add(num1: string, num2: string): string

// 具体实现
function add(num1: any, num2: any): any {
  if (typeof num1 === 'string' && typeof num2 === 'string') {
    return num1.length + num2.length
  }
  return num1 + num2
}

// 匹配导number类型的函数重载
const result = add(20, 30)
const result2 = add('abc', 'cba')
console.log(result, result2);

// 在函数重载中,具体的实现函数不能直接被调用
// 报错 没有定义对象类型的函数重载 直接调用实现函数错误
// add({ name: 'zs' }, { age"18})

其他

联合类型

function printID(id: number | string) {
  // 使用联合类型的时候 需要特别的小心
  // console.log(id.toUpperCase());  // 报错
  // narrow: 缩小 这里为缩小范围
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id);

  }
}

printID(123)
printID('abc')

交叉类型

交叉类型&

交叉类型真正的用武之地就是将多个接口类型合并成一个类型,从而实现等同接口继承的效果,也就是所谓的合并接口类型

type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
  id: 1,
  name: 'name',
  age: 18
}

类型别名

type用于定义类型别名,当然也可以使用interface接口

type IDType = string | number
type PointType = {
  x: number,
  y: number,
  z?: number,
}

function printId(id: IDType) { }

function printPoint(point: PointType) { }

类型断言as

const el = document.getElementById('zs')
const el = document.getElementById('zs') as HTMLImageElement

// el获得的元素太广泛了 包含了很多的元素类型
// src是img里面的属性 在其他的HTMLElement中不一定有 所以会报错
// 所以我我们需要使用类型断言as来拿到HTMLImageElement 就不会报错了
el.src = 'url'

// 把string转换为number类型是不行的 需要把string先转为any或unknown类型
const message = 'Hello World'
// const num: number = (message as any) as number
const num: number = (message as unknown) as number
console.log(num);

案例

class Person { }

class Student extends Person {
  studying() { }
}

function sayHello(p: Person) {
  // p.studying() // 报错
  (p as Student).studying()
}

const stu = new Student()
sayHello(stu)

非空类型断言

以下的编译是有问题的,因为message是可选的,当我们输入为空的时候,是拿不到length

function printMessageLength(message?: string) {
  console.log(message.length);
}

printMessageLength('Hello World')
printMessageLength()

怎么解决上面的问题呢?

使用if判断或者非空断言!,当然也可以使用ES11的可选链?.

function printMessageLength(message?: string) {
  console.log(message!.length);
}

printMessageLength('Hello World')
printMessageLength()

字面量类型

// 当我们使用let的时候,TS会推导出类型注解
// 当使用const的时候,ts会把字面量直接作为类型注解
let message1 = 'Hello World'  // string
const message2 = 'Hello TS' // Hello TS

// 字面量类型的意义,一般结合联合类型
type Alignment = 'left' | 'right' | 'center'
let align: Alignment = 'left'
align = 'right'
align = 'aaa' // 报错

字面量推理

type Method = 'GET' | 'POST'
function request(url: string, method: Method) { }

type Request = {
  url: string,
  method: Method
}

const options: Request = {
  url: 'https://www.xxx',
  method: 'POST'
}

// 如果在options中没有使用Request的话 options.method为字符串类型
// 或者在options.method后面添加as Method 这样就不需要在options后面添加类型注解了
// 也可以在options后面添加as const 这样里面的内容就是readonly 
request(options.url, options.method)

export { }

类型缩小

28-1-46

类型缩小(Type Narrowing

// 1.typeof的类型缩小
type IDType = number | string
function printID(id: IDType) {
  console.log(id);  // IDType

  if (typeof id === 'string') {
    console.log(id.toUpperCase());  // string
  } else {
    console.log(id);  // number
  }
}

// 2.平等的类型缩小 (=== == !== switch)
type Direction = 'left' | 'right' | 'top' | 'bottom'
function printDirection(direction: Direction) {
  if (direction === 'left') {
    console.log(direction); // left
  } else {

  }

  // switch (direction) {
  //   case 'left':
  //     console.log(direction);  // left
  //     break
  //   case ...
  // }
}

// 3.instanceof
function printTime(time: string | Date) {
  if (time instanceof Date) {
    console.log(time.toUTCString);
  } else {
    console.log(time);
  }
}

class Student {
  studying() { }
}

class Teacher {
  teaching() { }
}

function work(p: Student | Teacher) {
  if (p instanceof Student) {
    p.studying()
  } else {
    p.teaching()
  }
}

const stu = new Student()
work(stu)

// 4.in
type Fish = {
  swimming: () => void
}

type Dog = {
  running: () => void
}

const fish: Fish = {
  swimming() {
    console.log('swimming');
  }
}

function walk(animal: Fish | Dog) {
  if ('swimming' in animal) {
    animal.swimming()
  } else {
    animal.running()
  }
}

TypeScript中的类

TS里面需要在类里进行类型注解

class Person {
  name: string
  age: number = 0

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

  eating() {
    console.log(this.name + 'eating');
  }
}

const p = new Person('zs', 19)
console.log(p.name);
p.eating()

继承

class Person {
  name: string
  age: number = 0

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

  eating() {
    console.log(this.name + 'eating 100行');
  }
}

class Student extends Person {
  sno: number

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

  // 重写
  eating(): void {
    super.eating()
    console.log('student eating');
  }

  studying() {
    console.log('studying');
  }
}

const s = new Student('zs', 19, 1)
console.log(s.name);
s.eating()

多态

class Animal {
  action() {
    console.log('animal running');
  }
}

class Dog extends Animal {
  action(): void {
    console.log('dog running');
  }
}

class Fish extends Animal {
  action(): void {
    console.log('fish swimming');
  }
}

// 父类引用指向子类对象
// const animall: Animal=new Dog()
function makeActions(animals: Animal[]) {
  animals.forEach(animal => {
    animal.action()
  })
}

makeActions([new Dog(), new Fish()])

成员修饰符

TS中,类的属性和方法支持三种修饰符:public、private、protectec

  • public修饰的是在任何地方可见、共有的属性或方法,默认编写的属性就是public
  • private修饰的是仅在同一类可见、私有的属性或方法
  • protected修饰的是仅在类自身及子类中可见、受保护的属性或方法
class Person {
  protected name: string = 'zs'
}

class Student extends Person {
  getName() {
    return this.name
  }
}

const stu = new Student()
console.log(stu.getName());

readonly

只读属性,不能修改

class Person {
  readonly name: string
  age?: number
  readonly friend?: Person
  constructor(name: string, friend?: Person) {
    this.name = name
    this.friend = friend
  }
}

const p = new Person('zs', new Person('ls'))
console.log(p);

// friends是只读属性,不能修改
// 如果只读属性是一个对象,那么对象中的内容可以修改
// p.friend = new Person('ww')

if (p.friend) {
  p.friend.age = 30
}

访问器

class Person {
  private _name: string
  constructor(name: string) {
    this._name = name
  }

  set name(newName) {
    this._name = newName
  }

  get name() {
    return this._name
  }
}

const p = new Person('zs')
p.name = 'ls'
console.log(p.name);

静态属性

静态属性与静态方法是使用static修饰的属性与方法

class Student {
  static time: string = '8:00'

  static attendClass() {
    console.log('学习');
  }
}

console.log(Student.time);
Student.attendClass()

抽象类

29-2

抽象类abstract:

  • 抽象类不能实例化(被new出来)
  • 继承抽象类的子类,必须实现抽象类中抽象的方法(实现多态)
function makeArea(shape: Shape) {
  return shape.getArea()
}

abstract class Shape {
  abstract getArea()
}

class Rectangle extends Shape {
  private width: number
  private height: number

  constructor(width: number, height: number) {
    super()
    this.width = width
    this.height = height
  }

  getArea() {
    return this.width * this.height
  }
}

class Circle extends Shape {
  private r: number

  constructor(r: number) {
    super()
    this.r = r
  }

  getArea() {
    return this.r * this.r * 3.14
  }
}

const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)
// const shape = new Shape() 错误

console.log(makeArea(rectangle));
console.log(makeArea(circle));

类的类型

类也是可以作为类型注解的

class Person {
  name: string = '123'
  eating() { }
}

// p1必须与Peron定义的属性key一致
const p1: Person = {
  name: 'zs',
  eating() { }
}

// 场景
function printPerson(p: Person) {
  console.log(p.name);
}

// 传入参数的两种方法
printPerson(new Person())
printPerson({ name: 'ls', eating: function () { } })

TypeScript接口

声明对象类型

一种声明对象类型方式:接口interiface

// 通过type别名来声明对象类型
// type InfoType = { name: string, age: number }

// 另外一种声明对象类型方式:接口interface
// 第一个I为接口的意思 这是一个规范
interface IInfoType {
  readonly name: string
  age: number
  friends?: { name: string }
}

const info: IInfoType = {
  name: 'zs',
  age: 18
}

索引类型

interface IndexLanguage {
  [index: number]: string
}

// key为number类型 value为string类型
const frontLanguage: IndexLanguage = {
  0: "HTML",
  1: "CSS",
  2: "JavaScript",
  4: "Vue"
}

interface ILanguageYear {
  [name: string]: number
}

const languageYear: ILanguageYear = {
  "C": 1972,
  "Java": 1995,
  "JavaScript": 1996,
  "TypeScript": 2014,
}

函数类型

接口的实现

使用implements关键字可以实现一个或多个接口

interface ISwim {
  swimming: () => void
}

interface IEat {
  eating: () => void
}

class Animal {

}

// 继承:只能实现单继承
// 实现:实现接口,类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
  swimming() {
    console.log("Fish Swimming");
  }

  eating() {
    console.log("Fish Eating");
  }
}

class Person implements ISwim {
  swimming() {
    console.log("Person Swimming");

  }
}

function swimAction(swimable: ISwim) {
  swimable.swimming()
}

swimAction(new Fish())
swimAction(new Person())
swimAction({ swimming: function () { } })

interface与type区别

我们发现interfacetype都可以用来定义对象类型,那么在开发中选择哪一个呢?

  • 如果是定义非对象类型,通常使用type,比如Direction、Alignment或者一些Function

如果定义对象类型,那么它们是又区别的

  • interface可以重复定义某个接口来定义属性和方法

    // interface可以重复定义且它们会合并
    interface IFoo {
      name: string
    }
    
    interface IFoo {
      age: number
    }
    
    // 相当于
    // interface IFoo {
    //   name: string,
    //   age: number
    // }
    
    const foo: IFoo = {
      name: 'zs',
      age: 18
    }
    
    // 会与window进行合并 window中多了给age属性
    interface Window {
      age: number
    }
    
    window.age = 20
    console.log(window.age);
    
  • type定义的是别名,别名不能重复

    // type不能重复定义
    type IBar = {
      name: string
    }
    
    type IBar = {	// 报错
      age: number
    }
    

    字面量赋值

    擦除操作freshness,示例如下:

    interface IPerson {
      name: string
      age: number
      height: number
    }
    
    const info = {
      name: 'zs',
      age: 18,
      height: 1.88,
      address: '广州市'
    }
    
    function printInfo(person: IPerson) {
      console.log(person);
      // console.log(person.address); 报错
    }
    
    // 报错
    // printInfo({
    //   name: 'zs',
    //   age: 18,
    //   height: 1.88,
    //   address: '广州市'
    // })
    
    // 在这里ts会进行擦除操作 在虽然把info传进去了 但是不能拿到info中的address
    printInfo(info)
    
    // 这里也进行了擦除操作
    const p: IPerson = info
    
    console.log(info);
    console.log(p);
    

TypeScript枚举类型

// 枚举右默认值 默认等于0,往后递增
// 枚举的值是可以修改的
enum Direction {
  LEFT = 100, // 默认为0 修改后后面的值会递增 RIGHT=101 ... 也可以改成字符串
  RIGHT,
  TOP,
  BOTTOM
}

function turnDirection(direction: Direction) {
  console.log(direction);

  switch (direction) {
    case Direction.LEFT:
      console.log('改变角色的方向向左');
      break
    case Direction.RIGHT:
      console.log('改变角色的方向向右');
      break
    case Direction.TOP:
      console.log('改变角色的方向向上');
      break
    case Direction.BOTTOM:
      console.log('改变角色的方向向下');
      break
    default:
      const foo: never = direction
      break
  }
}

turnDirection(Direction.LEFT)

泛型

泛型就是类型参数化,例子如下

function sum<Type>(num1: Type): Type {
  return num1
}

// 1.调用方式一:明确传入类型
console.log(sum<number>(20));
console.log(sum<{ name: string }>({ name: 'zs' }));
console.log(sum<any[]>(['abc']));


// 2.调用方式二:类型推导
console.log(sum(50));
console.log(sum('abc'));

多个参数

// 剩余参数只能使用前面类型中的一个
function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {

}

foo<number, string, boolean>(10, 'abc', true, 10, 20, 30)
// 类型推导
foo(10, 'abc', true, 10, 20, 30)

泛型接口

interface IPerson<T1, T2> {
  name: T1
  age: T2
}

const p: IPerson<string, number> = {
  name: 'zs',
  age: 18
}

泛型类

class Point<T>{
  x: T
  y: T
  z: T

  constructor(x: T, y: T, z: T) {
    this.x = x
    this.y = y
    this.z = z
  }
}

const p1 = new Point<string>('1', '2', '3')
const p2: Point<string> = new Point('1', '2', '3')
// 类型推导
const p3 = new Point('123', '234', '345')

const names1: string[] = ['a', 'b', 'c']
const names2: Array<string> = ['a', 'b', 'c'] // 不推荐 因为可能会与jsx冲突

类型约束

TS中可以使用extends对泛型的类型进行约束

interface ILength {
  length: number
}

// 传递的类型需要有length这个属性
function getLength<T extends ILength>(arg: T) {
  return arg.length
}

console.log(getLength('abc'));
console.log(['a', 'b']);
console.log({ length: 100 });

模块化开发

TS支持两种方式来控制我们的作用域

  • 模块化:每个文件可以是独立的模块,支持ES Module,也支持CommonJS

    导出

    export function add(num1: number, num2: number) {
      return num1 + num2
    }
    
    export function sub(num1: number, num2: number) {
      return num1 - num2
    }
    

    导入

    import { add, sub } from './utils/math';
    
    console.log(add(20, 30));
    console.log(sub(20, 30));
    
  • 命名空间:通过namespace来声明一个命名空间

    • 同一模块中,命名空间中的内容需要导出,不然在外面拿不到
    • 在其他模块引用,需要把命名空间导出

    导出

    export namespace time {
      export function format(time: string) {
        return '2222-2-22'
      }
    
      function foo() { }
    
      export let name: string = 'zs'
    }
    
    namespace price {
      export function format(price: number) {
        return '99.99'
      }
    }
    
    // 命名空间中的内容需要导出 不然在外面拿不到
    console.log(time.format('2022'));
    time.name
    // time.foo  // 错误
    

    导入

    import { time } from './utils/format'
    
    console.log(time.name);
    

类型查找

30-2-20

之前我们所有的ts中的类型,都是我们自己编写的,但是我们也有用到其他的一些类型

const el = document.getElementById('zs') as HTMLImageElement

那么HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?

  • 其实这里就涉及到ts对类型的管理和查找规则了

我们来认识一种ts文件:.d.ts文件

  • 我们编写的ts文件都会输出为.js文件
  • .d.ts文件,它是用来做类型的声明(declare),它仅仅用来做类型检测,告知ts有那些类型

那么ts会在哪里查找我们的类型声明呢?

  • 内置类型声明
  • 外部定义类型声明
  • 自己定义类型声明

内置类型声明

内置类型声明是ts自带的、帮助我们内置了JS运行时的一些标准化API声明文件

  • 包括Math、Date等内置类型,也包括DOM API,比如Window、Document

内置类型说明通常在我们安装ts的环境会自带:

github.com/microsoft/T…

外部定义类型声明

外部类型什么通常是我们使用一些库(比如第三方库)时,需要的一些类型声明

这是库通常有两种类型声明方式:

  1. 在中间库中进行类型声明(编写.d.ts文件),比如axios

  2. 通过社区的一个公有库Definitely Typed存放类型声明文件

    • 该库的GitHub地址:github.com/DefinitelyT…

    • 该库查找声明安装方式的地址:www.typescriptlang.org/dt/search?s…

    • 比如我们安装react的类型声明与lodash的类型声明:

      npm i @types/react -D
      

      导入loadsh

      import lodash from 'lodash'; // 会报错
      
      // 安装后 不报错
      npm install @types/lodash -D
      

自定义类型声明

30-2-44

如果我们没有内置类型声明,也没有外部定义类型声明,那么我们该怎么办?

可以使用自定义类型声明

创建一个declare.d.ts文件,这个文件只是用来定义类型,不用赋值

// 声明模块
declare module 'lodash' {
  export function join(arr: any[])
}

// 声明函数/变量/类
declare let name: string
declare let age: number

declare function foo(): void

declare class Person {
  name: string
  age: number
  constructor(name: string, age: number)
}

// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'

declare module '*.vue' { }

// 声明命名空间
declare namespace $ {
  export function ajax(settings: any): any
}

index.html

<script>
  let name = 'zs'
  let age = 18

  function foo() {
    console.log('foo');
  }

  function Person(name, age) {
    this.name = name
    this.age = age
  }
</script>

这样引入就不会有问题了

import lodash from 'lodash';
// 在TS中直接引入文件也出错的 需要在.d.ts中声明
import img from './img/sunset_wide.png';

console.log(lodash.join(['abc', 'cba']));

// 这里能输出name与age 因为在html中定义有 这里的ts代码最终会被转为js代码放到html中的script中
console.log(name);
console.log(age);

foo()

const p = new Person('ls', 20)
console.log(p);

$.ajax({})