TypeScript笔记
安装
npm i -D typescript
ts不能直接在浏览器环境执行,需要先编译成js文件。
文件编译指令:
tsc [tsfile]
tsc [tsfile] -w 可以自动监视改变并重新编译
类型
number
string
boolean
字面量:声明变量可能的值,不能是别的值或者超出字面量的范围。let a: 10|11;a = 10;
any:任意值,可以被赋值不同类型的值。变量也可以赋值给其它类型的变量,不安全。
unknown:安全的any。可以是任何类型值。变量不允许直接被赋值给其它非unknown类型的变量。
void:空值null或者undefined。
never:不能是任何值。这种返回值一般用于系统报错throw newError()
object:任意js对象。
array:数组。
tuple:固定长度数组。[1,2]声明后就不能变长
enum:枚举。enum{'a', 'b'}
变量声明
let a: number;
a = 1;
let b: string = 'abc'
如果变量声明同时直接赋值,则类型会默认对应声明,
let a = false
a = 123 // 提示错误
sum(a:number, b:number): number { // 声明参数类型和函数返回值类型
return a + b
}
sum(1, '2') // 提示错误,但是编译还是会过,可以通过配置让编译也不通过
sum(1, 2, 3) // 提示错误,参数个数不匹配
使用字面量声明:类似于常量,声明了一个或者多个后,后面的代码赋值不能超出范围。
let a: 'hello'; // a的值只能是'hello'不能声明成别的
a = 'world' // 提示错误
let b: 'hello' | 'world'; // b的值只能是'hello'或者'world'
其他类型:
let a: any; // 不限制任何类型赋值
let b; // 如果声明变量不指定类型也不直接赋值,则类型同any
开发中一般不允许使用any类型,因为any类型的变量可以赋值给任何类型变量,会出现严重的问题:
let a;
a = 10;
a = true;
let b: number;
b = a; // tsc编译不报错
如果真有这种业务场景,推荐使用unknown。unknown相当于是一种安全的any,也可以赋值任意类型,但是不能随便赋值给其它类型的变量,直接复制会报错,如果实现进行过类型检查则可以赋值。
let a: unknown
a = 'hello'
let b: string
if (typeof a === 'string') { // 方式1
b = a
}
b = a as string // 方式2 使用断言,断言就是告诉编译器,这个变量的类型一定为指定类型
b = <string>a // 方式3 另一种断言
let a: object // js中一切皆对象,所以通常不会这样使用,实际上开发者关注的应该是对象的结构(属性方法什么的)
a = {}
a = function(){}
let b: {name: string, age?: number} // 这种使用方式比较推荐,使用b的时候必须设置name属性,age可选
b = {name: 'sony'}
let b: {name: string, [propName: string]: any} // 这种方式也比较推荐,[propName: string]: any表示可以追加任意自定义属性,propName是自定义的名字随便写,string表示属性名类型为字符串
b = {name: 'sony', a: 12}
let a: Function // 声明函数,但是实际上没人这么写,没什么意义,开发者关注的是函数的结构(参数返回值什么的)
let a: (arg1: number, arg2: number) => number // 函数的声明方式
let arr: Array<number> // 声明一个数组,内部元素类型为number
let arr: number[] // 作用和上面的等价
let arr: [number, number] // 声明定长数组,赋值的时候必须一一对应,这种方式的优势在于性能比较好,分配固定的内存空间就ok了
关于枚举enum,它的使用意图和“|”很类似,enum的优势在于兼顾了语义化和存储空间。
例如声明一个变量let a: '男'|'女',业务编码使用的时候没什么问题,但是数据库或者数据传递的时候通常倾向于更小的空间占用,比如存0或者1这种值,enum就可以满足这个需求。
开发中可以先定义一个enum类型的类,使用enum中的元素名就代表对应的值(编译器会默认赋值成0、1这种值)
enum Gender {
Male,
Female
}
let person: {name: string, gender: Gender}
person = {name: '张三', gender: Gender.Male} // 相当于gender: 0
另外,比较特殊的运算符“&”,声明类型的时候表示同时满足的意思:
let o: {name: string} & {age: number} // 表示变量o赋值的时候必须是一个对象并且同时有name和age属性。
额外提一下ts中的“?”和“!”的用法:
- 作为运算符:“?”用在三目运算符中(例如a?b:c),“!”表示取反(例如!a)
- 参数:“?”用在参数中表示可选项。例如function(a: string, b?: number)
- 成员变量:“?”表示可选项,“!”表示此项一定有值且不为null
- 安全链式调用:
?表示可能为null,如果为null就不往下执行,场景:a.b?.c();
!表示一定不为null,强制让编译器通过安全检查,场景:a.b!.c();或者a.b!.c = xxx
面向对象
ts的面向对象使用起来与es6基本一致:
// class声明和使用
class Animal {
static readonly type = '爬行动物' // 静态属性,readonly关键字表示只读
name = 'lucky' // 实例属性
// 构造函数
constructor(name: string = 'lucky') {
this.name = name
}
// 实例方法,同样,加了static就变成类静态方法了
eat() {
console.log(this.name + ' is eating...')
}
static move() {
console.log('animal can move')
}
}
const cat = new Animal('happy')
console.log(cat, Animal.type) // {name: 'happy'} '爬行动物'
console.log('eat: ')
cat.eat()
console.log('move: ')
Animal.move()
class Dog extends Animal {
eat() {
console.log('dog: ' + this.name + ' is eating...')
}
}
console.log(Dog.type)
const dog = new Dog()
console.log(dog, Dog.type)
dog.eat()
interface Human {
name: string
eat(): void
speak(): void
}
class Soldier implements Human {
name: string = '001'
private _level: number
constructor(name: string, level: number) {
this.name = name
this._level = level
}
get level(): number {
return this._level
}
set level(value: number) {
this._level = value
}
eat(): void {
console.log(`soldier[level ${this.level}] ${this.name} is eating...`)
}
speak(): void {
console.log(`soldier[level ${this.level}] ${this.name} is speaking...`)
}
}
const orcSoldier = new Soldier('crom', 1)
orcSoldier.eat()
orcSoldier.speak()
// 泛型,生产中不允许使用any类型,如果确实有场景要应对未知类型,或者类型是使用者自定义的,可以考虑使用泛型
function f<T, K>(t: T, k: K): T {
console.log(`t: ${t}, k: ${k}`)
return t
}
console.log(f(5, 3)) // 编译器自动识别类型
console.log(f<string, number>('hello', 1)) // 手动指定类型,更加严谨,推荐方式
es6目前无法支持抽象类、接口、私有属性修饰符private等面向对象的特性,ts编译器支持,但也只局限在检查层面,比如对私有属性赋值,虽然tsc检查错误但是默认仍然可以正常编译成js执行,所以需要配置中"noEmitOnError": true 避免最终代码出现逻辑漏洞。
编译器配置
ts文件的需要先经过编译器执行tsc指令编译成js文件才能在浏览器环境执行,开发过程中我们可以创建一个tconfig.json并根据需求配置参数,编译器会读取配置文件并覆盖默认配置。
ts配置文件跟普通json文件不太一样,里面可以写注释。
{
/* include:用于编译指定目录下的ts文件,**表示任意目录,*表示所有文件 */
"include": ["./src/**/*"],
/* exclude:不编译某些目录下的文件 */
/* extends:表示继承某些配置文件,用于根据需要拆分组合配置 */
/* files:直接编译指定文件名,用于小微型的应用开发,不常用 */
/* compailerOptions: 编译配置项,可以定制编译规则,可以让代码更严谨,减少程序出错概率 */
"compilerOptions": {
// target表示编译成什么样的文件,具体指某个es版本,默认会编译成es3,
// 通常会编译成es6(es2015)再配合webpack的babel-loader处理
"target": "ES2015",
// 指定模块化规范
"module": "ES2015",
// 指定用到了哪些库,方便ts编译器识别,比如用了dom就可以在代码中使用document否则会编译报错。
// 这项通常不需要配置,默认配置已经比较全。某些场景如nodejs环境或者用到一些默认不支持的lib会考虑配置
// "lib": ["DOM"]
// allowJS和checkJs一般成对使用,默认值是false,表示是否检查和编译js文件,
// 有些情况下可能出现js文件模块和ts文件模块编译后无法正常使用的问题,这时候会需要把js文件也编译处理一遍
"allowJs": true,
"checkJs": true,
"removeComments": true, // 是否移除注释,默认是false
// "noEmit": false // 默认false,不生成编译结果(target),不常用,某些情况下可能只是想使用编译检查功能的时候会设置这项
// 默认false,只要编译出错就不生成target,
// 在我们allowJs为true的时候,js文件即使检查有错误,也会执行编译,这可能会出现错误隐患,
// 如果要严格控制js文件和ts一视同仁,可以设置为true
"noEmitOnError": true,
// 严格模式的代码在浏览器环境下执行效率更好,
// 通常模块化引入的代码默认都是严格模式,有些非模块化代码如果也需要严格模式(开头加use strict),就需要配置此项
"alwaysStrict": true,
// 以下是一些常用代码安全检查规则
// 默认false,如果配置了strict为true,则所有规则选项(包括下面的选项)都会被覆盖为严格模式,如果想订制,则不要配置此项
// 开发中建议直接配置strict为true
// "strict": true,
// 不允许隐式any类型(不明确声明类型),默认是false,any会导致编译器不去判断类型,造成代码隐患
"noImplicitAny": true,
// 不允许隐式this(不明确声明this是什么类型),默认是false,原生js中this的指向非常灵活,跟动态调用环境有关系(执行上下文),
// 这也是bug隐患,如果是单纯声明一个function内部想用this,可以考虑把“this”作为参数传进来
"noImplicitThis": true,
// 是否严格检查null值,默认false,有些操作如获取dom,然后执行dom.xxx()的时候,dom可能获取失败即是null,这样执行就会报错,
// 为了避免这个隐患,编译器可以配置这个选项为true,代码中可以用if判断包裹,也可以使用ts语法dom?.xxx()
"strictNullChecks": true
}
}
ts-loader
开发中我们一般会使用webpack打包代码,使用ts-loader编译ts文件再通过babel-loader解决js的兼容性问题
ts-loader使用的时候建议先到官网上看下版本兼容相关信息,8.x和9.x分别对应webpack4 和webpack5
npm i -D ts-loader typescript
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{
test: /\.tsx?$/,
loader: "ts-loader",
exclude: /node_modules/
}
]
}
然后别忘了把tconfig.json放到项目根目录下