TypeScript使用详解

1,499 阅读13分钟

一、安装

全局安装

npm install typescript -g

查看版本

tsc --version

二、运行环境

方式一:webpack配置环境

npm init -y

安装依赖

npm i webpack webpack-cli typescript webpack-dev-server ts-loader html-webpack-plugin

package.json的配置

"scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "webpack",
    "serve": "webpack serve"
  },

创建一个tsconfig.json配置文件

tsc --init

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: {
    extensions: ['.js', '.cjs', '.ts', '.json']
  },
  module: {
    rules: [
      {
        test: /.ts$/,
        loader: 'ts-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: './index.html'
    })
  ]
};

在根路径上创建一个index.html

最后通过启动项目

npm run serve

方式二:ts-node

安装ts-node,另外ts-node需要依赖 tslib@types/node两个包

npm install ts-node tslib @types/node -g

运行ts文件

ts-node math.ts

三、变量的声明

1.变量的定义

var/let/const 标识符: 数据类型 = 赋值

2.number类型的使用

let num1: number = 12345
let num2: number = 0b101
let num3: number = 0o567
let num4: number = 0x45defconsole.log(num1, num2, num3, num4)
export {}

3.boolean类型的使用

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

4.string类型的使用

let message: string = 'hello world'
​
// 如果可以推断出类型可以不用写
let name = 'yogln'
let age = 18
​
export {}

5.array类型的使用

// const arr: Array<string> = [] // 不推荐(react jsx中是有冲突   <div></div>)const arr: string[] = [] // 推荐
​
arr.push('abc')
arr.push('bbc')
arr.push(123) // 报错export{}

6.object类型的使用

const obj = {
  name: 'yogln',
  age: 18
}
​
console.log(obj.name)

7.null和undefied类型

let n: null = null
let u: undefined = undefined

8.symbol类型的使用

let s1 = Symbol('title')
let s2 = Symbol('title')
​
const obj = {
  [s1]: '前端',
  [s2]: '开发'
}
​
export {}

四、TypeScript数据类型

1.any类型的使用

// 当进行一些类型断言 as any
// 在不想给某些JavaScript添加具体的数据类型时(原生的JavaScript代码是一样)
let message: any = 'Hello World'message = 123
message = true
message = {}

2.unknown类型的使用

// unknown类型只能赋值给any和unknown类型
// any类型可以赋值给任意类型
​
function foo(arg: unknown) {
  console.log(typeof arg)
}
​
foo('123')
foo(123)

3.void类型的使用

function sum(num1: number, num2: number): void {
  console.log(num1 + num2)
}
​
sum(20, 30)

void可以不写

4.never类型的使用

function handleMessage(message: number | string | boolean) {
  switch (typeof message) {
    case 'number':
      console.log('number类型的处理')
      break
    case 'string':
      console.log('strign类型的处理')
      break
    case 'boolean':
      console.log('boolean类型的处理')
      break
    default:
      const check: never = message
  }
}
handleMessage('abc')
handleMessage(123)
​
// 张三
handleMessage(true)

5.tuple元组类型的使用

// tuple 元组类型 多种元素的组合const info: [string, number, string] = ['yogln', 18, '前端']
const name = info[0]
console.log(name)
export {}

6.tuple元组类型的应用场景

// hook: useState
// const [counter, setCounter] = {counter: , setCounter:}function useState(state: any) {
  let currentState = state
  const changeState = (newState: any) => {
    currentState = newState
  }
  const tuple: [any, (newState: any) => void] = [currentState, changeState]
  return tuple
}
const [counter, setCounter] = useState(10)
​
export {}

当然我们也可以优化一下使用泛型

// hook: useState
// const [counter, setCounter] = {counter: , setCounter:}function useState<T>(state: T) {
  let currentState = state
  const changeState = (newState: T) => {
    currentState = newState
  }
  const tuple: [T, (newState: T) => void] = [currentState, changeState]
  return tuple
}
const [counter, setCounter] = useState(10)
​
export {}
​

五、其他类型的补充

1.函数的参数和返回值类型

// 给参数加上类型注解: num1: number, num2: number
// 给返回值加上类型注释: (): number
// 在开发中,通常情况下可以不写返回值的类型(自动推导)
function sum(num1: number, num2: number) {
  return num1 + num2
}
​
// sum(123, 321)

所以一般不用写返回值的类型

匿名函数可以通过推到得出参数的类型和返回值的类型

const names = ["abc", "cba", "nba"]
// item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
// 上下文中的函数: 可以不添加类型注解
names.forEach(function(item) {
  console.log(item.split(""))
})

2.对象类型

function printPoint(point: { x: number; y: number }) {
  console.log(point.x, point.y)
}
​
printPoint({ x: 2, y: 4 })

3.可选类型 ?

z?: number

function printPoint(point: { x: number; y: number; z?: number }) {
  console.log(point.x)
  console.log(point.y)
  console.log(point.z)
}
​
printPoint({ x: 123, y: 321 })
printPoint({ x: 123, y: 321, z: 111 })
​
export {}

4.联合类型 |

function print(id: number | string) {
  console.log(id)
}
print(123)
print('abc')
​
export {}

5.类型别名 type

type PointType = {
  x: number
  y: number
  z?: number
}
​
function printPoint(point: PointType) {
  console.log(point.x, point.y, point.z)
}
printPoint({ x: 2, y: 2 })

6.类型断言 as

// 1. 类型断言 as// <img id="why"/>
const el = document.getElementById('yogln') as HTMLImageElement
el.src = '图片的url'// 2. Person 是 Student 父类
class Person {}
class Student extends Person {
  studying() {}
}
function sayHello(p: Person) {
  ;(p as Student).studying()
}
const stu = new Student()
sayHello(stu)
​
// 3. as any/unknown
let message = 'hello world'
const num: number = message as any as number

7.非空类型断言 !.

打印可选类型的length

function printMsgLength(msg?: string) {
  console.log(msg!.length)
}
printMsgLength('hello world')

8.可选链的使用 ?.

type Person = {
  name: string
  age: number
  friend: {
    name: string
    girlfriend?: {
      name: string
    }
  }
}
​
const info: Person = {
  name: 'yogln',
  age: 18,
  friend: {
    name: 'why'
  }
}
​
console.log(info.friend?.name) // why
console.log(info.friend?.girlfriend?.name) //undefined
export {}

9.!!运算符

const message = 'Hello World'
​
console.log(!message) //false
console.log(!!message) //true

10.??运算符

let message: string | null = 'Hello World'
​
const content = message ?? '你好啊, 李银河'
// const content = message ? message: "你好啊, 李银河"
console.log(content) //你好啊, 李银河

11.字面量类型 let num: 123 = 123

const msg: 'hello world' = 'hello world'
let num: 123 = 123 // num 字面量类型的值必须和字面量相同
// num = 234 // 报错
​
type Alignment = 'left' | 'right' | 'center'
let alignment: Alignment = 'left'
alignment = 'center'

12.字面量类型的推理

type Method = 'GET' | 'POST'function request(url: string, method: Method) {}
​
const options = {
  url: 'https://www.yogln.com',
  method: 'POST'
} as const
request(options.url, options.method)
​
export {}

13.类型缩小

  • typeof 类型缩小

    //  1.typeof的类型缩小
    type IdType = string | number
    function printType(id: IdType) {
      if (typeof id === 'string') {
        console.log(id.toUpperCase())
      } else {
        console.log(id)
      }
    }
    
  • 平等类型缩小

    // 2. 平等类型缩小
    type Direction = 'left' | 'right'
    function pintDirection(direction: Direction) {
      // 1.if判断
      // if (direction === 'left') {
      //   console.log(direction)
      // } else if ()
      // 2.switch判断
      // switch (direction) {
      //   case 'left':
      //     console.log(direction)
      //     break;
      //   case ...
      // }
    }
    
  • instanceof

    // 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)
    
  • in

    // 4. in
    type Fish = {
      swimming: () => void
    }
    ​
    type Dog = {
      running: () => void
    }
    ​
    function walk(animal: Fish | Dog) {
      if ('swimming' in animal) {
        animal.swimming()
      } else {
        animal.running()
      }
    }
    const fish: Fish = {
      swimming() {
        console.log('swimming')
      }
    }
    walk(fish)
    

六、TypeScript函数

1.函数的类型

  • 定义常量的时候函数的编写的类型
type AddType = (num1: number, num2: number) => number
const add: AddType = (num1: number, num2: number) => {
  return num1 + num2
}
  • 函数作为函数的参数的时候
// 1. 函数做参数
function bar() {}
type FnType = () => void
function foo(fn: FnType) {
  fn()
}
foo(bar)

2.函数类型的案例

function calc(n1: number, n2: number, fn: (n1: number, n2: number) => number) {
  return fn(n1, n2)
}
​
const res1 = calc(20, 30, function (n1, n2) {
  return n1 + n2
})
console.log(res1) //50
const res2 = calc(20, 30, function (n1, n2) {
  return n1 * n2
})
console.log(res2) //600

3.函数的可选类型

函数的可选类型必须写在必选类型的后面

// y -> undefined | number
function foo(x: number, y?: number) {
​
}
​
foo(20, 30)
foo(20)

4.函数参数的默认值

function foo(num1: number, num2: number = 20) {
  console.log(num1, num2)
}
foo(20) // 20, 20

5.函数的剩余参数

function total(initial: number, ...nums: number[]): number {
  let res = initial
  for (const num of nums) {
    res += num
  }
  return res
}
console.log(total(20, 30)) //50
console.log(total(20, 30, 40)) //90

6.this的默认推导

const obj = {
  name: 'yogln',
  eating() {
    console.log(this.name + ' eating')
  }
}
obj.eating() //yogln eating

7.this的绑定

type ThisType = {
  name: string
}
​
function eating(this: ThisType, msg: string) {
  console.log(this.name, msg)
}
​
const info = {
  name: 'yogln',
  eating: eating
}
// 隐式绑定
info.eating('哈哈哈') // yogln 哈哈哈// 显式绑定
info.eating.call({ name: '显式绑定' }, '显式绑定') // 显式绑定 显式绑定
export {}

注意callapply的区别

apply把需要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn一个个的传递

fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);

bind语法和call一模一样,区别在于立即执行还是等待执行

fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行

8.函数的重载

function add(num1: number, num2: number): number
function add(num1: string, num2: string): stringfunction add(num1: any, num2: any): any {
  if (typeof num1 === 'string' && typeof num2 === 'string') {
    return num1.length + num2.length
  }
  return num1 + num2
}
​
console.log(add(123, 321))
console.log(add('yogln', 'ts'))
export {}

七、TypeScript中类的定义

1.类的定义

class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  eating() {
    console.log(this.name + ' eating')
  }
}
​
const p = new Person('yogln', 18)
console.log(p)
p.eating()

2.类的继承

class Person {
  name: string
  age: number
​
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
​
  eating() {}
}
​
class Student extends Person {
  status: string
  constructor(name: string, age: number, status: string) {
    super(name, age)
    this.status = status
  }
  // 重写父类方法
  eating() {
    console.log(this.name + ' eating')
  }
}
​
const s = new Student('yogln', 18, 'student')
console.log(s)
s.eating()

3.类的多态

class Animal {
  moving() {
    console.log('animal moving')
  }
}
​
class Dog extends Animal {
  moving() {
    console.log('dog running')
  }
}
​
class Bird extends Animal {
  moving() {
    console.log('bird flying')
  }
}
​
function makeMove(animals: Animal[]) {
  animals.forEach((animal) => {
    animal.moving()
  })
}
makeMove([new Dog(), new Bird()])
// dog running
// bird flying

4.成员属性private

私有成员属性,创建的实例和外部没办法直接调用和修改,可以调用里面的方法进行获取和调用

class Person {
  private name: string = ''
​
  setName(name: string) {
    this.name = name
  }
​
  getName() {
    return this.name
  }
}
​
const p = new Person()
// console.log(p.name)// 直接报错
console.log(p.getName()) // 
p.setName('yogln')
console.log(p.getName()) // yogln

5.成员属性 protected

保护成员属性外部也没法直接调用,但是他的子类继承可以使用

class Person {
  protected name: string = 'yogln'
}
​
class Student extends Person {
  getName() {
    return this.name
  }
}
​
const stu = new Student()
console.log(stu.getName()) // yogln

6.只读属性readonly

  1. 只读属性可以在构造器中进行赋值
  2. 属性本身没法修改,但是如果他是对象类型,对象中的属性值是可以修改的
class Person {
  readonly name: string
  age?: number
  readonly friend?: Person
​
  constructor(name: string, friend?: Person) {
    this.name = name
    this.friend = friend
  }
}
​
const p1 = new Person('yogln')
// p1.name = 'why' // 直接报错const p2 = new Person('yogln', new Person('Jhon'))
if (p2.friend) {
  p2.friend.age = 20
}
console.log(p2.friend)

7.setter和getter

类中的私有成员是不能进行访问的,这个时候我们可以使用sertter和getter

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('yogln')
p.name = 'kobe'
console.log(p.name)

8.类的静态成员属性static

可以不用创建实例直接调用类中的属性和方法

class Student {
  static stuName: string = 'yogln'
​
  static studying() {
    console.log(this.stuName + ' studying')
  }
}
​
console.log(Student.stuName) //yogln
Student.studying() // yogln studying

9.抽象类acstracts

抽象类中的类名和方法都需要abstract进行修饰

abstract class Shape {
  abstract getArea(): number
}
​
class Rectangle extends Shape {
  private x: number
  private y: number
  constructor(x: number, y: number) {
    super()
    this.x = x
    this.y = y
  }
  getArea() {
    return this.x * this.y
  }
}
​
class Circle extends Shape {
  private radius: number
  constructor(radius: number) {
    super()
    this.radius = radius
  }
​
  getArea() {
    return this.radius * this.radius * 3.14
  }
}
​
const circle = new Circle(2)
console.log(circle.getArea()) //12.56
​
const rectangle = new Rectangle(2, 3)
console.log(rectangle.getArea())// 6

八、接口的使用

1.声明对象类型

interface InfoType {
  readonly name: string
  age: number
  friend?: {
    name: string
  }
}
​
const info: InfoType = {
  name: 'yogln',
  age: 18
}
​
console.log(info)//{ name: 'yogln', age: 18 }

2.定义对象的索引类型

// 通过interface来定义索引的类型
interface IndexLanguage {
  [index: number]: string
}
const frontLange: IndexLanguage = {
  [0]: 'vue',
  [1]: 'react'
  // [a]: 'html' //报错
  // b: 'css' //报错
}

3.接口的继承

interface ISwim {
  swimming: () => void
}
interface IFly {
  flying: () => void
}
​
interface IAction extends ISwim, IFly {}
const action: IAction = {
  swimming() {},
​
  flying() {}
}

4.交叉类型

声明类型的时候

// 另一种组件类型的方式: 交叉类型
type NeverType = number & string
// 这其实是不可能存在的,所以,NeverType返回的类型是never

接口中的类型

interface Swim {
  swimming: () => void
}
interface Fly {
  flying: () => void
}
type MyType1 = Swim | Fly
type MyType2 = Swim & Fly
const obj1: MyType1 = {
  flying() {}
}
const obj2: MyType2 = {
  flying() {},
  swimming() {}
}

当对象的返回类型是两个的时候,必须实现里面所有的方法

5.接口的实现

接口定义后,也是可以被类实现的:

如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入,这就是面向接口开发.

interface ISwim {
  swimming: () => void
}
interface IFly {
  flying: () => void
}

class Person implements ISwim, IFly {
  swimming() {}
  flying() {}
}

function swim(swim: ISwim) {
  swim.swimming()
}

const p = new Person()
swim(p)

6.interface和type的区别

一般来说,非对象类型,建议使用type,而对象类型typeinterface都可以,那么我们怎们用呢?

interface IFoo {
  name: string
}

interface IFoo {
  age: 18
}

const foo: IFoo = {
  name: 'yogln',
  age: 18
}

可以看出interface可以重复定义属性和方法,而且定义的属性都被合并在了一起,但是type就不可以,而且type的别名是不可以重复的,这就是他们的区别

7.字面量赋值

interface IPerson {
	name: string;
	age: number;
}

const info: IPerson = {
	name: 'yogln',
	age: 18,
	address: 'zh' // 直接报错
}

我们发现info对象里面比接口的定义多了一个address就直接报错了,如果我们采用下面的方式赋值

interface IPerson {
  name: string
  age: number
}
const obj = {
  name: 'yogln',
  age: 18,
  address: 'zh' // 不会报错
}

const info: IPerson = obj
console.log(info) // { name: 'yogln', age: 18, address: 'zh' }

这是为什么呢?

这是因为TypeScript在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制。 但是之后如果我们是将一个 变量标识符 赋值给其他的变量时,会进行freshness擦除操作。

8.枚举类型

enum Direction {
  LEFT,
  RIGHT,
  TOP,
  BOTTOM
}

function trunDirection(direction: 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
  }
}

9.枚举类型的值

枚举类型是有默认值的

enum Direction {
  LEFT, // 0
  RIGHT, // 1
  TOP, // 2
  BOTTOM //3
}

我们也可以为他们赋其他类型值

enum Direction {
  LEFT = "LEFT",
  RIGHT = "RIGHT",
  TOP = "TOP",
  BOTTOM = "BOTTOM"
}

我们也可以

let d: Direction = Direction.LEFT

九、认识泛型

1.泛型的定义方式

function sum<T>(arg: T) {
  return arg
}

sum<number>(1)
sum<string>('abc')
sum<any[]>(['abc'])

2.泛型的参数类型

平时在开发中我们可能会看到一些常用的名称:

  • T:Type的缩写,类型
  • K、V:key和value的缩写,键值对
  • E:Element的缩写,元素
  • O:Object的缩写,对象
function foo<T, E, O>(arg1: T, arg2: E, arg3: O) {}

foo<number, string, boolean>(10, 'yogln', true)

3.泛型接口的使用

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

const p: IPerson = {
  name: 'yogln',
  age: 18
}

4.泛型类的使用

class Person<T1, T2> {
  x: T1
  y: T2

  constructor(x, y) {
    this.x = x
    this.y = y
  }
}

const p: Person<string, number> = new Person('yogln', 18)

5.泛型的类型约束

想要打印具有length的长度

interface ILength {
  length: number
}

function printLength<T extends ILength>(arg: T) {
  return arg.length
}

printLength('abcd')
printLength(['a', 'b', 'c'])
printLength({ length: 100 })

十、模块化

1.模块化开发

TypeScript两种方式支持我们的作用域

  • 模块化:每个文件是一个独立的模块
  • 命名空间:通过namespace来声明一个命名空间

当我们导出两个函数名称相同的函数的时候,是直接报错的

export function format() {
  return 'timeFormat'
}

export function format() {
  return 'priceFormat'
}

我们可以修改函数的名称不同,我们也可以使用namespace

export namespace time {
  export function format() {
    return 'timeFormat'
  }
}

export namespace price {
  export function format() {
    return 'priceFormat'
  }
}

导入的时候

import { time } from './utils/format'

console.log(time.format())

2.类型的查找

当我们在ts中使用

const imgEl = document.getElementById('image') as HTMLImageElement

大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方 法呢?这里就涉及到typescript对类型的管理和查找规则

我们这里先给大家介绍另外的一种typescript文件:.d.ts文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型

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

  1. 内置类型声明;
  2. 外部定义类型声明;
  3. 自己定义类型声明

内置类型声明

内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件; 包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等; 内置类型声明通常在我们安装typescript的环境中会带有的

github.com/microsoft/T…

外部定义类型声明

外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明,这些库通常有两种类型声明方式:

  • 方式一:在自己库中进行类型声明(编写.d.ts文件),比如axios

  • 方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件

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

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

    比如我们安装react的类型声明: npm i @types/react --save-dev

比如我们安装了lodash,我们想要使用,就可以在这个网站搜索进行安装相关的依赖

image-20210726174419798.png

自己定义类型声明

什么情况下需要自己来定义声明文件呢?

  • 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
  • 情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;

比如:我们在index.html文件中定义了nameage,想在main.ts中使用

<script>
  let name = 'yogln'
  let age = '18'
</script>
console.log(name)
console.log(age)

直接使用肯定是不行的,我们需要建一个.d.ts文件,名字位置都可以随便,用来声明要使用的变量

declare let name: string
declare let age: number

这个时候就好了

声明函数声明类也是同样的道理

declare function foo(): voiddeclare class Person {
  name: string
  age: number
​
  constructor(name: string, age: number)
}

声明模块lodash

declare module 'loadsh' {
  export function join(args: any[]): any
}