typeScript学习笔记
安装与编译
安装
npm install -g typescript
查看版本
tsc -v
编译
tsc yourCode.ts
配置
配置文件为
tsconfig.json
- 文件选项配置
files
: 表示编译需要编译的单个文件列表
"files": [
// 指定编译文件是src目录下的a.ts文件
"scr/a.ts"
]
include
: 表示编译需要编译的文件或目录
"include": [
// "scr" // 会编译src目录下的所有文件,包括子目录
// "scr/*" // 只会编译scr一级目录下的文件
"scr/*/*" // 只会编译scr二级目录下的文件
]
exclude
: 表示编译器需要排除的文件或文件夹
默认排除node_modules
下的文件
"exclude": [
// 排除src目录下的lib文件夹下的文件不会编译
"src/lib"
]
extends
: 引入其他配置文件,继承配置
// 把基础配置抽离成tsconfig.base.json文件,然后引入
"extends": "./tsconfig.base.json"
compileOnSave
:设置保存文件的时候自动编译
"compileOnSave": true
************* vscode中上述功能的实现需要运行一个任务 *************
↓↓↓↓↓↓ vscode下实现保存文件自动编译 ↓↓↓↓↓↓
- 编译选项配置
compilerOptions
:配置编译选项
编译选项配置非常繁杂,有很多配置,这里只列出常用的配置。
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
references
指定工程引用依赖
如果你后端是用node写的可能用得着这个 ^ _ ^ :
在项目开发中,有时候我们为了方便将前端项目和后端node项目放在同一个目录下开发,两个项目依赖同一个配置文件和通用文件,但我们希望前后端项目进行灵活的分别打包,那么我们可以进行如下配置:
Project
- src
- client //客户端项目
- index.ts // 客户端项目文件
- tsconfig.json // 客户端配置文件
{
"extends": "../../tsconfig.json", // 继承基础配置
"compilerOptions": {
"outDir": "../../dist/client", // 指定输出目录
},
"references": [ // 指定依赖的工程
{"path": "./common"}
]
}
- common // 前后端通用依赖工程
- index.ts // 前后端通用文件
- tsconfig.json // 前后端通用代码配置文件
{
"extends": "../../tsconfig.json", // 继承基础配置
"compilerOptions": {
"outDir": "../../dist/client", // 指定输出目录
}
}
- server // 服务端项目
- index.ts // 服务端项目文件
- tsconfig.json // 服务端项目配置文件
{
"extends": "../../tsconfig.json", // 继承基础配置
"compilerOptions": {
"outDir": "../../dist/server", // 指定输出目录
},
"references": [ // 指定依赖的工程
{"path": "./common"}
]
}
- tsconfig.json // 前后端项目通用基础配置
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"composite": true, // 增量编译
"declaration": true
}
}
这样配置以后,就可以单独的构建前后端项目:
- 前端项目构建
tsc -b src/client
- 后端项目构建
tsc -b src/server
- 输出目录
Project
- dist
- client
- index.js
- index.d.ts
- common
- index.js
- index.d.ts
- server
- index.js
- index.d.ts
类型约束
对于基本类型只需通过冒号+类型即可实现类型约束
默认情况下null
、undefined
当做属于任何类型,即即使声明了number类型,该变量也可以是null或者undefined
除非设置了:"strictNullChecks": true,
// 不允许把null、undefined赋值给其他类型的变量,null 和 undefined 获得了它们自己各自的类型null
和undefined
类型
let str:string = '哈哈哈'
str = 1 //会有错误提示,因为str不是number类型
数组、元组、函数类型约束
let arrOfNums:number[] = [1,2,3]
arrOfNums.push('1')//会有错误提示,因为规定了该数组的成员只能为number类型
let user:[string,number]
user=['李雷',16]//正确
user=['李雷',16,18]//会有错误提示,因为user的成员类型、数量、位置已经被限定了
//可以这样突破限制,但是push的值也只能是元组限定的类型,即 string|number
user=['李雷',16]
user.push(1)//正确
user.push(true)//会有错误提示,因为[string,number]类型中没有boolean类型的成员
?
表示可选参数,可选参数后不能有确定参数,不然会出错
function add(x:number,y:number,z?:number):number{
if(z){
return x+y+z
}else{
return x+y
}
}
//或者
const add = (x:number,y:number,z?:number):number => {
//...
}
//此时add有了一个类型 (x:number,y:number,z?:number | undefined):number
Interface -接口
作用:用于描述和限定对象应该由什么属性、方法构成。
readonly
修饰符表示该属性和方法在定义后就不可以修改了。
interface Human{
name:string;
readonly age:number;
six:boolean;
study?:(content:string)=>void;
}
//study可有可无,因为它是可选的,但是如果你一旦有这个了,就得和interface里规定的类型一致。
//对象中多出来interface里没有的属性和方法是不可以的。
//对象中缺失interface里规定的必须有的项是不可以的。
let lilei:Human={
name:'lilei',
age:18,
six:true,
study:(content:string)=>{
console.log('我学习了' + content)
}
}
//不可以,因为age为readonly
lilei.age=17
一般情况下 interface 的命名用大写的 I 开头比较规范。例如:interface IHuman{…}
不过eslint有的限制会禁止你使用 I 开头来命名interface
类型推论 联合类型 类型断言
类型推论
:ts的机制,在没有明确指定类型的时候,对类型做出推论
let str="哈哈哈"
str=1 //不可以,因为str在赋值时被限定为string类型
联合类型
:表示变量或属性可以是多种类型中的一个 例如:string | number
注意
:联合类型只能访问这些类型共有的属性和方法。
function fn(numOrStr:number | string):void{
//联合类型只能访问这些类型共有的属性和方法。
console.log(numOrStr.length) //不可以哦,因为number类型上没有length属性。
}
类型断言
:主动断言变量可以有的一个确定类型,可解决上述问题。
用as
关键字断言。
function fn(numOrStr:number | string):void{
//类型断言
console.log((numOrStr as string).length) //可以哦,因为断言了这个变量的类型,所以可以访问了。
}
在判断类型的条件语句中,同样可以起到上述效果
支持的关键字为typeof
和instanceof
function fn(numOrStr:number | string):void{
if(typeof numOrStr === 'string'){
console.log(numOrStr.length)//可以的哦,该条件分支自动帮我们缩小了范围,不会有错误提示
}
}
class -类
class在ES6中已有使用,ts对它有更多的支持
访问修饰符
public
:在该类、子类、实例对象都可以访问。
protected
:能在该类、子类中访问,实例对象不可以访问。
private
:仅能在该类中访问。
静态修饰符
static
:表示该属性或方法为类的静态属性或方法,实例上就没有这个属性或方法,所以只能用类名调用,不能用this哦~
通过以下代码理解他们:
class Human {
public name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Man extends Human {
static gene:string="(*&&(&*&(*&^&*&^&"
private wife:string | null;
constructor(name: string, age: number, wife: string | null) {
super(name,age)
this.wife=wife
// this.gene="*&&^&*^*&^%$%$$" //this是调用不了类的静态成员的
}
public writeGene=(newGene:string):void=>{
Man.gene=newGene //想要调用gene,只能用类名来调用,因为gene是Man类型的静态属性
}
}
let lilei=new Man("lilei",18,null)
//console.log(lilei.wife) //不能访问,属性"wife"为私有属性,只能在类"Man"中访问。
//console.log(lilei.age) //不能访问,属性"age"受保护,只能在类"Human"及其子类中访问。
console.log(lilei.name)
接口和类的搭配使用
关键字:
implements
:用于类实现接口
interface Radio{
switchRadio(trigger:boolean):void;
}
interface Battery{
checkBatteryStatus():void;
}
class Cellphone implements Radio,Battery{
//必须实现 Radio 和 Battery 接口
switchRadio(trigger:boolean){
//some code
}
checkBatteryStatus(){
//some code
}
}
接口可以继承,也是用
extends
关键字
interface Radio{
switchRadio(trigger:boolean):void;
}
//此时若要实现 RadioWithBattery 接口,就要实现 switchRadio 和 checkBatteryStatus 两个方法
interface RadioWithBattery extends Radio{
checkBatteryStatus():void;
}
枚举
对一组常量的统一管理,关键词
enum
- 基本使用
enum season{
spring,
summer,
autumn,
winter
}
console.log(season.spring) //0
console.log(season.summer) //1
console.log(season.autumn) //2
console.log(season.winter) //3
console.log(season[0]) //spring
console.log(season[1]) //summer
console.log(season[2]) //autumn
console.log(season[3]) //winter
- 如果给其中的某些成员赋值数字
一个原则:后面成员的值为前一个成员的值+1
赋值操作会覆盖默认值,但后面的值依然遵从这个原则
请看代码
enum season{
spring,
summer,
autumn = 0.5,
winter
}
console.log(season.spring) //0
console.log(season.summer) //1
console.log(season.autumn) //0.5
console.log(season.winter) //1.5
console.log(season[0]) //spring
console.log(season[1]) //summer
console.log(season[0.5]) //autumn
console.log(season[1.5]) //winter
- 如果值重复了会怎么样?(都是数字的情况下)
以下代码解决你的困惑
enum season{
spring,
summer,
autumn = 1,
winter
}
console.log(season.spring) //0
console.log(season.summer) //1
console.log(season.autumn) //1
console.log(season.winter) //2
console.log(season[0]) //spring
console.log(season[1]) //autumn
console.log(season[2]) //winter
//如果数字有重复,那通过数字索引来找成员名,会找到最后赋值这个数字的成员
- 赋值其他类型会怎样?
四个原则:
1.当赋的值为boolean类型
或者引用类型
的时候会出错。错误为:只有数字枚举可具有 计算成员,但此表达式的类型为 XXX。如果不需要全面性检查,请考虑改用对象文本。
2.当赋值不为数字时(合法的类型还有:string
、null
、undefined
),下一个成员必须得赋值。(即会有错误提示:枚举成员必须具有初始化表达式。)
3.如果有成员赋值了string
类型,那么就不能有成员被赋值为null
或者undefined
,否则会有错误提示:含字符串值成员的枚举中不允许使用计算值。
4.只有成员的值为数字时可以双向映射,即:
console.log(season.spring) //0
console.log(season[0]) //spring
其余成员只能通过成员名找到相应的值。
enum menbers{
a,
b,
c="哈哈哈",
d=0.5,
e,
f
}
console.log(menbers.a) //0
console.log(menbers.b) //1
console.log(menbers.c) //哈哈哈
console.log(menbers.d) //0.5
console.log(menbers.e) //1.5
console.log(menbers.f) //2.5
console.log(menbers[0]) //a
console.log(menbers[1]) //b
console.log(menbers[0.5]) //d
console.log(menbers[1.5]) //e
console.log(menbers[2.5]) //f
enum menbers1{
a,
b,
c=null,
d=undefined,
e=0.5,
f
}
console.log(menbers1.a) //0
console.log(menbers1.b) //1
console.log(menbers1.c) //null
console.log(menbers1.d) //undefined
console.log(menbers1.e) //0.5
console.log(menbers1.f) //1.5
console.log(menbers1[0]) //a
console.log(menbers1[1]) //b
console.log(menbers1[0.5]) //e
console.log(menbers1[1.5]) //f
泛型
你可以理解成让类型参数化,以便更灵活地进行类型约束的一种方式。它的特征是一对尖括号<>。
<>中的参数表示类型
- 泛型在函数中的使用——泛型函数
interface IT1 {
length: number;
}
interface IT2 {
push: (...items: any[]) => number;
}
//约束了T1,T2应该有哪些属性或方法
function fn<T1 extends IT1, T2 extends IT2>(p1: T1, p2: T2): T2 {
p2.push(p1.length)
return p2
}
//假如第一个参数不是个数组而是个数字就会报错,因为第一个参数类型规定为T1类型,而T1类型被限定必须有length属性
let arr = fn([1, 2, 3, 4, 5], ["a", null, 2])
console.log(arr)
//可以进一步进行约束,如下
//<>里写明的类型必须满足T1、T2的约束,而函数的2个实参必须是<>里对应的类型
let arr1 = fn<string, Array<number>>("adffewww", [1, 2, 3])
console.log(arr1)
- 泛型在类中的使用——泛型类
class Queue<T>{
private data = [];
push(item:T){
return this.data.push(item)
}
pop():T{
return this.data.shift()
}
}
//这个<>里的number就限定了queue.push的参数类型和queue.pop的返回值类型都得为number,因为在Queue中做了泛型约束
const queue =new Queue<number>()
queue.push(1)
console.log(queue.pop().toFixed())
- 泛型在接口中的使用——泛型接口
interface KeyPair<T, U> {
key: T
value: U
}
//接口中各成员的类型可以灵活约束
let kp1: KeyPair<number, string> = { key: 1, value: "string" }
let kp2: KeyPair<string, number> = { key: "str", value: 2 }
- 小知识
//这两句代码是等效的。Array是ts内置的一个泛型类
var arrTwo: number[]
var arrTwo: Array<number>
类型别名,字面量,交叉类型
- 类型别名:给类型起给别名,关键词
type
type T1 = (param1: number[]) => number;
type T2 = string | number;
let fn: T1
fn = (arr: number[]) => {
return arr.sort((a, b) => { return a - b })[0]
}
let strOrNum: T2
strOrNum = "哈哈哈"
strOrNum = 123
- 字面量:一种特殊的类型,是一个原始数据类型的数据。该类型的变量或属性只能是该原始数据的值。
//一个简单的字面量
let a:"哈哈哈"
a="哈哈哈" //没错它只能赋值"哈哈哈",赋值其他任何的值都是会提示错误的
//字面量和联合类型的配合使用
type T1 = "哈哈哈" | 1 | true | null | undefined
let b:T1
b="哈哈哈" //它只能赋值T1里那些字面量的值
- 交叉类型:和联合类型对应,表示该类型为两种或两种以上类型的全部属性和方法的总和。关键字
&
interface IName {
name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: "李雷", age: 17 } //这两个属性,不能少一个,也不能多出来其他属性
声明文件
因为有时我们使用的有些库不是ts编写的,所以我们在使用库时需要一份声明文件
XXX.d.ts
这个文件是用来声明库里的变量、方法、属性等(没有的话,当我们使用库里提供的变量、方法、属性时会出现:找不到名称XXX、XXX不存在属性XX等错误)
关键词declare
- 全局库
声明:在全局库中,可以在全局变量中声明普通变量,函数,对象等。
interface UserInfo {
id: string,
name: string,
age: number
}
// 变量
declare var userInfo: UserInfo;
// 函数
declare function getUserName (id: string): string;
// 对象
declare namespace MyLib {
const $userInfo: UserInfo
}
// 类
declare class User {
constructor(args: UserInfo);
}
- 全局插件
声明:一个全局插件是全局代码,它们会改变全局对象的结构。 比如,一些库往Array.prototype或String.prototype里添加新的方法。
interface String {
startWithSomeThing(some?: string): string
}
- 模块化的库
模块导出为对象
声明:
// my-module/index.d.ts
// 导出类型
export interface UserType {
name: string,
age: number,
}
// 常量
export const version: number;
// 方法
export function getUserName(id: string): string;
// 对象
export namespace $appState {
function getName(): void;
}
// 如果要在全局导出一个命名空间来代表这个声明文件,则可以:
export as namespace MyModule;
导入:
// ts模块规范
import * as MyModule from 'my-module';
// commonjs规范
const MyModule = require('my-module');
// TypeScript
import MyModule = require('my-module');
// 引入部分导出
import { version, $appState } from 'my-module';
模块导出为函数:
声明:我们可以声明多个函数实现函数的重载。
// my/index.d.ts
declare function MyFunction(name: string): MyOverloaded.NamedReturnType
declare function MyFunction(length: number): MyOverloaded.LengthReturnType
declare namespace MyOverloaded {
export interface LengthReturnType {
width: number,
height: number,
}
export interface NamedReturnType {
firstName: string,
lastName: string,
}
}
export = MyFunction;
ts文件里
import MyFunction from "my";
MyFunction("哈哈哈").firstName
MyFunction(123).height
模块导出为一个类
声明
declare namespace Person {
export interface Param {
id: string;
name: string;
age: 20;
}
}
declare class Person {
constructor(someParam?: Person);
getName(id: string): string;
}
export = Person;
工具类型
这些ts内置类型js中没有,是ts为了开发者能够更灵活的定义类型而存在的。
typeScript官网关于Utility types的文档
别人整理的Utility types用法