简介
关于为什么要学习ts, ts相对于 js 有什么好处, 我在这篇文章有简要介绍, 接下里我们将学习ts的基本使用, 学习其中的语法, 如果学习过 像java 这种强类型的语言,那么学ts会很轻松.
上手
首先安装, 初始化 tsconfig.json
// 安装 ts
yarn add typescript --dev
// 生成 tsconfig.json配置文件
yarn tsc --init
在配置文件中有许多配置选项,简单介绍个重要的属性
- target: ts要编译成的版本, 默认是 'es5', 如果改成 'es2015',那么 es6 就不会被编译了.
- module: 输出的代码将会按照什么方式去模块化, 默认是 'commonjs',那么默认导入导出就是 require和 module.exports 的方式
- outDir: ts文件编译后的文件夹, 一般是dist目录
- rootDir: 需要编译的文件夹, 一般是 src
- sourceMap: boolean类型值, 可以查找ts内部文件
- strict: boolean类型, 开启会对类型检查非常严格, 每个值都需要声明类型
ts 原始数据类型
// 原始数据类型
const a: string = 'foobar'
const b: number = 100 // NaN Infinity
const c: boolean = true // false
// 在非严格模式(strictNullChecks)下,
// string, number, boolean 都可以为空
// const d: string = null
// const d: number = null
// const d: boolean = null
const e: void = undefined
const f: null = null
const g: undefined = undefined
还有一个 原始类型比较特殊, Symbol,这是在 es2015 新增的特性, 但是ts 默认编译成的是 es5, 所以要使用 Symbol, tsconfig.json中的 lib 选项必须包含 ES2015
object类型
object 类型是指除了原始类型以外的其它类型, 像数组, 方法,对象都属于 object
const foo: object = function () {} // [] // {}
// 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' }
数组类型 与 元组类型
// 数组类型的两种表示方式
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
// 元组类型
const tuple: [number, string] = [18, 'zce']
// 可以通过解构的方式拿到值
const [age, name] = tuple
枚举类型
// 标准的数字枚举
enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2
}
// 数字枚举,枚举值自动基于前一个值自增
enum PostStatus1 {
Draft = 6,
Unpublished, // => 7
Published // => 8
}
// 字符串枚举
enum PostStatus2 {
Draft = 'aaa',
Unpublished = 'bbb',
Published = 'ccc'
}
const enum PostStatus3 {
Draft, // 默认从 0开始
Unpublished,
Published
}
const post = {
status: PostStatus3.Draft // 使用方法
}
数组类型
数组类型主要是为了声明参数类型
// 可以赋默认值, 可以 以...形式获取剩余参数
function func1 (a: number, b: number = 10, ...rest: number[]): string {
return 'func1'
}
func1(100, 200)
func1(100)
func1(100, 200, 300)
any类型
any类型是弱类型, 主要是为了兼容以前的老项目, 不建议使用
隐式类型推断
注意下面两段代码的区别,前面一个会报错因为 age被隐式推断为number类型, 后面的不会报错因为 age被隐式推断为any类型
let age = 18. // ts 推断 age 为 number 类型
age = 'string' // 会报错, 因为 age是 number类型
let age // ts 推断 age 为any类型
age = 18 // any 类型不会报错
age = 'string' // any 类型不会报错
断言
在一些特殊的情况下, ts 是无法推断出一些变量的具体类型,请看下面的例子
res * res 为什么会报错呢?
看到报错提示就应该明白了, res 有可能是 underfined, 那这个时候我们该怎么办呢? 可以用 ts的断言
通过 as 关键字 告诉ts我们确定res就是number,我们还有另外一种方式断言
const num2 = <number>res
通过在 res 前面加尖括号方式断言, 这种方式在 react jsx语法中因为冲突原因不能使用, 所以建议使用第一种方式.
接口
接口最直观的体现就是约定一个对象只能有哪些成员,而且这些成员的类型也固定了. 例如:
// 定义一个接口
interface Post {
title: string
content: string
}
// 传入的参数对象必须按照接口来
function printPost (post: Post) {
console.log(post.title)
console.log(post.content)
}
printPost({
title: 'Hello TypeScript',
content: 'A javascript superset'
})
当然, 在接口中也有可选属性, 在 属性后面加一个问号就代表该属性可有可无, 还可以在接口前面加 readonly,代表该属性 只读,不能被修改. 例如下面的例子
interface Post {
title: string
content: string
subtitle?: string
readonly summary: string
}
类
class Person {
name: string // 一般需要在头部声明 属性
age: number
constructor (name: string, age: number) {
// 这里初始化, 当然在 外面声明再赋值也是可以的
this.name = name
this.age = age
}
sayHi (msg: string): void { // 这里就是对函数 约定,无返回值, 传入参数为 字符串
console.log(`I am ${this.name}, ${msg}`)
}
}
学过 java 的应该知道, public, protected, private的作用是什么
class Person {
public name: string // = 'init name'
private age: number
protected gender: boolean
constructor (name: string, age: number) {
this.name = name
this.age = age
this.gender = true
}
}
就比如上面的类, 如果没有声明上面三者其一, 就会默认声明 为 public, 这种属性在我们实例化之后是可以直接拿到的, 但是 private 在实例化之后是拿不到了, 也就是私有属性. protected是只能在继承的 类里面拿到, 比如还有一个 Student 类继承了 Person 类, 那么 在 Student内部可以拿到 gender, 实例化的 Student也同样是拿不到的.
类和接口
相比于类, 接口是比较抽象的, 我们首先来看一个例子
class Person {
eat (food: string): void {
console.log(`优雅的进餐: ${food}`)
}
run (distance: number) {
console.log(`直立行走: ${distance}`)
}
}
class Animal {
eat (food: string): void {
console.log(`呼噜呼噜的吃: ${food}`)
}
run (distance: number) {
console.log(`爬行: ${distance}`)
}
}
上面有两个类, 两个类都有同样的两个方法,那么我们就可以实现一个接口来约束这两个类必须要有 eat 和 run 方法
interface EatAndRun {
eat(food: string): void;
run(distance: number): void;
}
class Person implements EatAndRun {
eat(food: string): void {
console.log(`优雅的进餐: ${food}`);
}
run(distance: number) {
console.log(`直立行走: ${distance}`);
}
}
class Animal implements EatAndRun {
eat(food: string): void {
console.log(`呼噜呼噜的吃: ${food}`);
}
run(distance: number) {
console.log(`爬行: ${distance}`);
}
}
通过 implements 关键字 实现 EatAndRun 接口 ,如果在类里面有个方法没有实现就会报错. 但是 一般来说一个接口实现一个功能就行, 如果Person 不想实现run方法怎么办呢? 我们可以这么做
interface Eat {
eat (food: string): void
}
interface Run {
run (distance: number): void
}
class Person implements Eat, Run {
eat (food: string): void {
console.log(`优雅的进餐: ${food}`)
}
run (distance: number) {
console.log(`直立行走: ${distance}`)
}
}
类是可以通过 逗号实现多个 接口的, 所以如果只需要实现一个那就 implements 一个.
抽象类
抽象类与接口比较类似, 有一个区别就是 抽象类 可以具体实现某个方法
abstract class Animal {
eat (food: string): void {
console.log(`巴拉巴拉: ${food}`)
}
abstract run (distance: number): void
}
class Dog extends Animal {
run(distance: number): void {
console.log('四脚爬行', distance)
}
}
const d = new Dog()
d.eat('啦啦啦')
d.run(100)
就比如上面的例子, eat 方法可以直接实现, 然后也可以通过 abstract 关键字定义抽象方法. 接着在Dog 类中通过 extends 关键字 继承 抽象类, 需要注意的是抽象类中的抽象方法也必须被实现.
泛型
泛型指的是在我们定义 函数、接口或者类的时候没有指定具体的类型,等到使用的时候才去指定具体的类型,泛型的作用就是极大程度的复用代码.
以函数为例,
function createNumberArray (length: number, value: number): number[] {
const arr = Array<number>(length).fill(value)
return arr
}
function createStringArray (length: number, value: string): string[] {
const arr = Array<string>(length).fill(value)
return arr
}
上面两个函数, 一个是返回数字类型的数组, 一个是返回 字符串类型的数组, 可以看到两个函数长得几乎一样, 那么有什么方法可以去复用呢?
function createArray<T> (length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const res = createArray<string>(3, 'foo')
我们通过在函数名后面加尖括号的方式自定义一个类型T, 然后value声明为T, 返回值为 T类型的数组,在我们调用的时候 就可以按照自己的需求传递一个类型达到复用的目的.
类型声明
在我们开发的过程中,一般都会用到第三方的 npm 模块,而这么模块并不一定是通过 typescript 编写的,所以说它提供的成员就不会有强类型的体验,比如loadash包
我们安装了一个lodash库, 使用里面的一个camelCase方法, 我们知道 它返回的是一个驼峰格式的字符串,传入的是一个字符串,可是我们看到并没有提示, 这个时候我们需要声明这个方法
声明之后我们发现就有提示了, 这种 decalre 其实就是为了兼容普通的js模块. 由于现在 ts社区是比较强大的,一些常用的 npm模块都已经提供了对应的声明, 我们只需要安装对应的类型声明模块就可以了. 比如loadsh, 对应的声明模块 是 @types/lodash 安装过后就有智能提示了
当然,现在也有很多 npm模块内置了 声明模块,不需要我们再安装额外的声明模块