目录

一、TypeScript是什么

JavaScript是一种弱类型语言,你不需要声明变量的类型就可以使用它。看似非常灵活而且方便,但是在大型系统中,类型的不确定会带来很多麻烦。事实上前端很大一部分的bug都是因为类型错误。

TypeScript在JavaScript的基础上扩展了类型系统,它是JavaScript的超集,可以编译为 JavaScript执行。使用TypeScript最大的好处就是,它能帮助你在编译时就发现类型错误。

二、为什么要用TypeScript

  • 使用TypeScript之前:TypeScript是什么?

使用TypeScript之后:JavaScript是什么?

  • TypeScript是JavaScript的未来趋势,前端主流框架目前都支持(或即将支持)TypeScript
  • 在编译阶段发现类型错误,这在开发大型项目时非常有必要

三、TypeScript基本语法

### 1) 类型声明

  • 在创建变量时声明类型
    变量类型声明只需要在变量名后面加上“冒号 类型”

    let foo: string = 'abc'
  • 声明函数的入参和返参
    函数的入参类型是在括号内加冒号,返参类型是在括号后面加冒号

    function bar ( a: number, b: number ): boolean {
      return a > b
    }

### 2) 基本类型
| js原始类型 | |
|---|---|
|布尔值|boolean|
|数值|number|
|字符串| string|
|undefined|undefined|
|null|null|

| ts扩展类型| 示例 | 解释 |
|---|---|---|
|数组|number[]Array<number>|表示一个number类型的数组|
|元组(Tuple)|[string, number]|元组是一种复合类型的数组,具有固定的数组长度,且每个元素可以是不同类型|
|any|any|如果你不确定某个变量的类型,可以声明为any|
|void|void|void一般用于函数返参的声明,表示函数没有返回值|

注:还有一些类型,如 enum, never, object,因使用范围较窄,这里没有列出
### 3) 类型断言
考虑一种情景,你从某个接口获得了一些数据res,ts编译器默认它的类型是any,但是实际上你知道它的类型是number[],这时你可以使用类型断言,让ts知道它实际的类型。

// 假装从接口获得了res, 此时它的类型是any
let res: any = [1, 2, 3]

// 两种断言方式
// 1. 尖括号断言
let data: number[] = <number[]>res
let len: number = (<number[]>res).length

// 2. as断言
let data: number[] = res as number[]
let len: number = (res as number[]).length

### 4) 类型推论
直接看示例

let text = 'abc'
let num = 123
let data = res as number[]

在这些例子中,我们并没有声明变量的类型,但是ts已经通过推论知道了它的类型,这就是类型推论。
下面这个示例展示了函数返回值的类型推论

function foo () {
  return false
}
// ts会推论出x的类型是boolean
let x = foo()

可以应用类型推论的地方有很多,这里不一一细讲,请自行体会。
### 5) 接口(interface)
重要的东西来了

接口用来定义一个对象的结构(在某些其他的语言中叫做结构体)

interface Cat {
  age: number
  color: string
}
let myCat: Cat = {
  age: 3,
  color: 'orange',
}

在上面这个例子中,我们定义了一种类型Cat,它具有两个属性:agecolor,然后创建了一个Cat类型的变量myCat

接口的属性也可以是function

interface Cat {
  age: number
  color: string
  scratch (something: any): void {
    // ...
  }
}

我们并不是必须用interface关键字来声明结构,在下面的例子中,我们定义了scratch函数的入参结构

function scratch (something: { name: string, price: number }): void {
  // ...
}

接口的属性可以是可选的,在属性名后面加上?,表示这个属性不一定存在

interface Cat {
  age: number
  color: string
  weight?: number
}

嵌套结构

interface Cat {
  age: number
  color: string
  like: {
    food: string,
    toy: string
  }
}

### 6) 类(class)
我们先来声明一个js版本的Cat

// js版本的class
class Cat {
  constructor (age: number, color: string) {
    this.age = age
    this.color = color
  }
}

ts的class与js稍有不同,在js中,我们不需要特别声明类的属性,你可以在任何时候使用this.xxx来添加属性。
而在ts中,我们必须明确的声明类的属性

// ts版本的class
class Cat {
  age: number
  color: string
  constructor (age: number, color: string) {
    this.age = age
    this.color = color
  }
}

属性可以声明默认值(会在constructor执行之前自动进行赋值)

class Cat {
  age: number
  color: string = 'orange'
  weight?: number
  constructor (age: number, color?: string) {
    this.age = age
    this.color = color || this.color
  }
}

class关键字在声明一个类的同时,也声明了一个同名的接口,你既可以把它当做class来使用,也可以当一个type来使用。

let cat: Cat = new Cat(3, 'orange')
let fakeCat: Cat = {
  age: 3,
  color: 'orange'
}

### 7) 复合类型(Advanced Types)

  • 交叉类型(Intersection Types)

    interface Cat {
      name: string
      scratch (): void
    }
    interface Dog {
      name: string
      bite (): void
    }
    type Animal = Cat & Dog
    let animal: Animal = {
      name: 'animal',
      scratch () {},
      bite () {}
    }

    如以上示例,我们定义了两个类型Cat和Dog,然后定义了一个新的类型Animal,Cat & Dog表示它将具有Cat和Dog的全部属性

  • 联合类型(Union Types)

    type Pet = Cat | Dog

    联合类型与交叉类型很像,但是意思完全不同,它表示Pet只能是Cat和Dog其中的一种,以下示例更能说明这一点

    function accept (input: string | number): number {
      return Number(input) || 0
    }

    函数accept接收一个字符串或数值,然后将其转换为数值。input可以是string或number

  • 字面量类型

    let priority: '高' | '中' | '低' = '低'

    我们定义了一个变量priority,它的取值只能是'高' | '中' | '低'中的一个
    有时我们也需要用数字来表示

    let priority: 1 | 2 | 3 = 3

### 8) 工具类型(Utility Types)
Typescript内置了一些工具类型,比较实用有Partial<T>, Readonly<T>, Pick<T,K>, Omit<T,K>等等
例如Partial<T>可以将一个接口的属性变成可选

interface Cat {
  name: string
  age: number
}
function update (cat: Cat, props: Partial<Cat>): Cat {
  return Object.assign({}, cat, props)
}
let cat: Cat = {
  name: 'miu',
  age: 3
}
cat = update(cat, { age: 4 })

update函数用来更新cat的属性,Partial<Cat>表示这个新生成的类型具有Cat的一部分属性
Tips: Partial<T>是一个泛型(Generics), 关于它的解释请查阅官网

四、在你的项目中使用TypeScript

  • Typescript依赖安装

要让webpack解析ts文件,你需要安装typescriptts-loader
如果你使用esLint,还需要安装@typescript-eslint/parser

  • webpack构建配置(Vue项目)

首先要在rule中添加一条规则,让webpack识别.ts.tsx文件,并使用ts-loader加载

{
  test: /\.tsx?$/,
  exclude: /node_modules/,
  use: [
    "babel-loader",
    {
      loader: "ts-loader",
      options: { appendTsxSuffixTo: [/\.vue$/] }
    }
  ]
}

然后是.vue文件也需要支持ts。我们先来看'.vue'的规则配置(不同项目写法可能有差异,但结构大体一致)

{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    loaders: {
      css: generateLoaders(),
      less: generateLoaders(),
      // ...
    },
    cssSourceMap: sourceMapEnabled,
    cacheBusting: config.dev.cacheBusting,
    // ...
  }
}

.vue文件首先会经过vue-loader加载,template, script, style会被拆分成不同的文件,再由对应的加载器进行加载。例如<style lang="less">会被options.loaders.less模块加载。所以为了让.vue支持ts,我们需要在options.loaders中添加ts-loader

{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    loaders: {
      css: generateLoaders(),
      less: generateLoaders(),
      // ...
      ts: 'ts-loader',
      tsx: "babel-loader!ts-loader"
    },
    cssSourceMap: sourceMapEnabled,
    cacheBusting: config.dev.cacheBusting,
    // ...
  }
}
  • Typescript配置文件

可以在项目根目录放置Typescript配置文件tsconfig.json,推荐配置如下

{
  "compilerOptions": {
    "strict": true, 
    "strictNullChecks": false,
    "noImplicitThis": false,
    "noImplicitAny": false,
    "target": "ES5",
    "module": "esnext",
    "lib": [
      "esnext",
      "es2015",
      "es2016",
      "dom"
    ],
    "moduleResolution": "node",
    "experimentalDecorators": true,    
    "allowSyntheticDefaultImports": true,    
    "downlevelIteration": true,
    "baseUrl": ".",    
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "jsx": "preserve"
  },
  "include": [
    "./src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

配置项的意思可以去Typescript官网查看

  • eslint配置

要让eslint支持Typescript,需要在.eslintrc中添加如下配置

{
  parser: "vue-eslint-parser",
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 6,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    }
  },
}

五、参考资料


Unreal
87 声望10 粉丝