目录
一、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
,它具有两个属性:age
和color
,然后创建了一个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文件,你需要安装typescript
和ts-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
}
},
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。