前言
在我接触TypeScript之前也困惑,我js写得好好的,怎么突然多出一种新的语言,难道又要从入门到放弃的学习过程了吗?脑壳疼!刚开始我接触TypeScript语法api的时候,也是各种迷惑,但随意项目的使用和更深入的学习,只能说真香。而且和我想象的并不是一样,TypeScript绝对不是简简单单掌握几个类型,它也是一种需要反复使用才能熟练精进的语言。
Typescript是什么
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯JavaScript,主要提供了类型系统和对 ES6 的支持。所以其实TypeScript是我们编写代码时的规范,因为JavaScript一直被人诟病的其中之一问题是没有类型约束规范,很多bug都是类型错误导致的,而TypeScript很好的弥补了这个问题,所以可以看成TypeScript是JavaScript语言的丰富,而不是完全的俩种语言。
优势与不足
- 优点
- 在开发时提供了静态类型检查和语法提示(可用变量、方法、类提示),可以让我们开发更严谨,提前发现错误,减少改Bug时间
- 类型系统提高了代码可读性,维护和重构代码更加容易,而且可以通过这个可以帮助我们更快理解代码逻辑
- 补充了接口、枚举等开发大型应用时JS缺失的功能
- 不足
- 增加了学习成本,在写代码的时候需要花更多的时间思考怎么写好类型约束,尤其是刚不熟悉的时候,只想写any,但我们熟悉到一点程度,而且项目到了一定层级的时候,只能说这个是小case
安装环境
首先我们创建个文件夹,初始化个项目,安装TypeScript配置tsc --init
npm install typescript --save-dev
初始化TypeScript配置
tsc --init
//然后我们可以看到多了一个tsconfig.json文件
tsconfig.json文件指定了编译项目所需的根目录下的文件以及编译选项,这是Typescript文件生成的默认配置
{
"compilerOptions": {
"target": "es2016", /* 用于设置ts编译成js的语言版本 */
"module": "commonjs", /* 指定生成哪个模块系统代码: "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"。 */
"esModuleInterop": true, /* 支持使用import d from 'cjs'的方式引入commonjs包 */
"forceConsistentCasingInFileNames": true, /* 禁止对同一个文件的不一致的引用 */
"strict": true, /* 启用所有严格类型检查选项 */
"skipLibCheck": true /* 忽略所有的声明文件( *.d.ts)的类型检查 */
}
}
然后在package.json增加一条执行指令
"scripts": {
"dev":"tsc -w"
},
这样我们修改ts文件就自动生成了相应的js文件,-w是持续监听ts文件的修改
tips:当命令行上指定了输入文件时,
tsconfig.json文件配置项会被忽略
关于tsconfig.json配置
- files - 设置要编译的文件的名称
- include - 设置需要进行编译的文件,支持路径模式匹配
- exclude - 设置无需进行编译的文件,支持路径模式匹配
- compilerOptions配置置与编译流程相关的选项
- 关于
compilerOptions配置
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
接下来我们开始愉快学习Typescript相关知识点
基础类型
string
let name:string='mike'
boolean
let flag:boolean=true
number
let age:number=20
Array
数组类型有俩种写法
const arr: number[] = [1,2,3];
const arr2: Array<number> = [1,2,3];
元组 Tuple
元组其实就是已知数组的长度和每一项的类型,当获取超出数组下标的长度时会提示异常
let arr:[number,string,boolean]=[1,'abc',true]
let a1=arr[2] /* true */
let a2=arr[3] /* 'error' */
对象类型
对象类型有三种表示方式:object、Object、{}
object:除了string | boolean | number | bigint | symbol | null | undefined,其它值都可以赋值给object,严格模式下,null 和 undefined 类型也不能赋给 object
let object: object;
object = 1; //错误
object = "a"; //错误
object = true; //错误
object = null; // 正确,严格模式错误
object = undefined; // 正确,严格模式错误
object = {}; // 编译正确
Object: Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)
let bigObject: Object;
bigObject = 1; // 正确
bigObject = "a"; // 正确
bigObject = true; // 正确
bigObject = null; // 正确
bigObject = undefined; // 正确
bigObject = {}; //正确
{}:
{} 与 Object 的效果几乎一样,即 {} == Object,但 Object 更规范
let obj:{}={}
obj.a='11' //错误
枚举
枚举类型用于常量数据的集合。通过枚举数据的使用,我们可以清晰地看出这组数据的作用以及相应的值
enum Color {Red, Green, Blue}
let c1: Color = Color.Red; /* 1 */
let c2: Color = Color.Green; /* 2 */
let c3: Color = Color.Blue; /* 3 */
默认情况下,如果我们没有指定成员的数值,将会从从数值0开始递增
enum Color {Red, Green=3, Blue}
let c1: Color = Color.Red; /* 0*/
let c2: Color = Color.Green; /* 3*/**
let c3: Color = Color.Blue; /* 4 */
如果中间某个成员有指定数值项,下一项将会从当前项开始递增
- 用于字符串指定值枚举
enum Color {Red='red', Green='green', Blue='blue'}
let c1: Color = Color.Red; /* red*/
let c2: Color = Color.Green; /*green*/
let c3: Color = Color.Blue; /* blue */
null和undefined
在Typescript,也有相应的null和undefined类型
let a: undefined = undefined; /* 正确 */
let b: null = null; /* 正确 */
let c:null='sdds' /* 错误 */
一般分配了null和undefined类型,就不能赋值给其它类型
let a: string = '11111';
let b: number = 6666
a=null /* 正确 */
b=undefined /* 正确 */
默认情况下 null 和 undefined 是所有类型的子类型。也就是说你可以把 null 和 undefined 赋值给其他类型。
如果你在tsconfig.json指定了"strictNullChecks":true ,即开启严格模式后, null 和 undefined 只能赋值它们各自的类型。
// 启用 --strictNullCheckslet
let a:string = '11111';
let b: number = 6666
a=null /* 错误 */
b=undefined /* 错误 */
any
当如果如果不清楚变量类型的时候,可以用any会绕过类型检查
let notSure: any = 4
notSure = "dsds" /* 正确 */
notSure = false /* 正确 */
但是这不意味着我们可以随便乱用any,如果把变量都写成了any,那就失去了我们使用Typescript初衷了
void
表示没有任何类型,一般用在函数值返回上
function a():void{}
never
never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型(死循环)
// 抛出异常
function error(message: string): never {
throw new Error(message);
}
// 死循环,永远不会有返回值
function infiniteLoop(): never {
while (true) {
}
}
unknown
unknown与any一样,所有类型都可以分配给unknown
let a: unknown = 1;
a = "mike"; /* 正确 */
a = false; /* 正确 */
任意类型的值都是可以复制给
any与unknown二者,any会绕过类型检查,直接可用,而unkonwn则必须要在判断完它是什么类型之后才能继续用,any就是个自行车辅助轮, 习惯了 Typescript 的强类型检查规则应该尽快扔掉,使用类型更安全的unkown。
高级类型
函数
函数声明
function add(x: number, y: number): number {
return x + y;
}
函数表达式
```js
const add = function(x: number, y: number): number {
return x + y;
}
函数重载
使用相同名称和不同参数数量或类型创建多个方法的一种能力
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
函数重载真正执行的是同名函数最后定义的函数体在
最后一个函数体定义之前全都属于函数类型定义,不能写具体的函数实现方法,只能定义类型
class类
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHi(): void {
console.log(`Hi, ${this.name}`);
}
}
type
类型别名用来给一个类型起个新名字,使用 type 创建类型别名,类型别名不仅可以用来表示基本类型,还可以用来表示对象类型、联合类型、元组和交集。
// 基础类型
type Name = string;
type Arr = number[];
// 函数
type GetName=(name:string)=>string
// 联合类型
type UserId = string | number;
// 对象
type Person = {
id: number;
name: string;
age: number;
gender: string;
};
interface
和类型别名相似,它是用来命名数据结构的方式,可以用来描述对象类型、函数
// 对象类型
interface Person {
id: number;
name: string;
age: number;
gender: string;
};
// 函数类型
interface Person1 {
(x:number,y:number):number
}
- type和interfeace区别
- 都可以用来定义对象
类型、函数,type还可以用于其它类型定义 - 都可以实现
继承,interface会进行同名合并,而type不会
- type
type Person={
name:string,
age:number
}
// 相同名字的type不会合并,会提示异常
// type Person={
// height:number
// }
type Person1 = Person&{
getName(name:string):string
}
let person:Person1={
name:'mike',
age:12,
getName(name){
return name
}
}
- interface
interface Person{
name?:string, /*可选*/
readonly age:number /*只读*/
}
interface Person{
height:number
}
// 相同名字的接口时,interface会合并,等价于
// interface Person{
// name:string,
// age:number
// height:number
// }
interface Person1 extends Person{
getName(name:string):string
}
let person:Person1={
name:'mike',
age:12,
height:150,
getName(name){
return name
}
}
- 关于使用
公共的最好用
interface实现,不能用interface实现的再用type实现。主要是一个项目最好保持一致。
泛型
在介绍泛型之前,我们可能会遇到个这样的场景,输入参数和输出参数的类型一样,输入参数可以是任意类型,我们首先可能想到这样写
function printInfo(info:any):any{
return info
}
但这样就失去了我们使用Typescript的意义了,接下来就是泛型出场时机了
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
function printInfo<T>(info:T):T{
return info
}
我们预先定义了一个类型,然后在输入类型和输出参数赋予,这样当我们在使用函数时,输入和输出参数类型一致了
- 多个参数的使用
function printInfo<T,U>(age:T,name:U):[T,U]{
return [age,name]
}
- interface接口的使用
interface Person <T,Y> {
name:T;
age:Y,
getInfo(name:T,age:Y):[T,Y]
}
let person:Person<string,number>={
name:'Mike',
age:16,
getInfo(name,age){
return [name,age]
}
}
- class类的使用
class Person<T> {
name:T;
constructor(name: T){
this.name = name;
}
getName(name:T) {
console.log(name)
}
}
let cat = new Person('Mike');
cat.getName('John')
工具类型
typeof:在类型上下文中获取变量或者属性的类型
- 获取
接口的类型
interface Person {
name:string,
age:number
}
let person:Person={
name:'Mike',
age:11
}
let person1:typeof person={
name:'John',
age:20
}
- 获取
函数的类型
function getName(x: number): Array<number> {
return [x];
}
type Fn = typeof getName; // -> (x: number) => number[]
- 获取
class的类型
class A {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
};
// 这里 typeof A ------> new (x: number, y: number) => number;
function getInstance(PointClass: typeof A, x: number, y: number) {
return new PointClass(x, y);
}
// // 下面写法将报错
// function getInstance2(PointClass: Ponit, x: number, y: number) {
// return new PointClass(x, y);// 报错 此表达式不可构造。类型 "Ponit" 没有构造签名。
// }
class B{
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
getInstance(B,11,22) /* 正常 */
keyof:可以用来获取一个对象接口中的所有 key 值
interface Person {
name:string,
age:number
}
type Person1=keyof Person /* 等价于 type Person1='name'|'age' */
in:用来遍历枚举类型
type Animal='pig' |'dog'|'mouse'
type Animal1={
[key in Animal]:string
}
extends:用来继承、泛型约束以及三元表达式里面的判断
- 继承
类继承类
class User {
name:string;
age:number;
constructor(name: string, age:number) {
this.name = name;
this.age = age;
}
getInfo() {
console.log(`${this.name}已经${this.age}了`);
}
}
class Person1 extends User {
constructor(name: string, age:number) {
super(name,age);
}
say(){
console.log('我在说话')
}
}
let person1=new Person1('Mike',13)
person1.getInfo()
首先我们定义了一个User类,该类有name和age属性以及getInfo方法。然后又定义了一个继承User类的Person1类,并且在该类上又添加了一个say方法。
接口继承接口
interface A {
name:string
}
interface B extends A {
age:number
}
let b:B={
age:11,
name:'Mike'
}
定义了一个接口A,然后用接口B继承接口A属性,这样接口B同时拥有name和age属性
接口继承类
class User {
name:string;
constructor(name: string) {
this.name = name;
}
getName() {
console.log(`我的名字是${this.name}`);
}
getAge(age:number){
console.log(`${this.name}已经${age}岁了`)
}
}
interface Person extends User{
goActivity(name:string):void
}
let person:Person={
name:'Mike',
getName(){},
getAge(age:number){
console.log(`${this.name}已经${age}岁了`)
},
goActivity(name:string){
console.log(`参加了${name}活动`)
}
}
person.getAge(13) //Mike已经13岁了
person.goActivity('足球') //参加了足球活动
定义了一个User类,然后接口Person继承了User类,这样接口Person上定义的属性方法变成name、getName、getAge、goActivity
类继承接口implements(类实现接口)
类继承接口不再是使用关键字extends去实现,而是通过implements
interface User {
name:string,
age:number
}
class Person implements User {
name:string='Mike'
age:number=20
}
let person=new Person()
person.name // Mike
implements关键字将类User当作一个接口,这意味着类Person必须去实现定义在User中的所有方法,无论这些方法是否在类User中有没有默认的实现。同时,也不用在类Person中定义super方法。
类实现多个接口
interface User {
name:string,
age:number
}
interface User1 {
say(desc:string):void
}
class Person implements User ,User1{
name:string='Mike'
age:number=20
say(desc:string){
console.log('说了什么:',desc)
}
}
let person=new Person()
person.name // Mike
person.say('11111') // 说了什么:11111
类Person实现了User、User1上的接口,所以Person必须去定义User、User1上面的属性方法
2.泛型约束
假设一开始我们一开始不知道某个类型,但我们想要使用某个类型的方法和属性
function getLength<T>(arg:T):T {
console.log(arg.length); // 报错,因泛型T上并没有length属性
}
这时候我们可以通过extends的方式来实现对泛型约束
interface Length {
length: number;
}
function printLength<T extends Length>(arg: T): T {
console.log(arg.length);
return arg;
}
printLength(3) /* 错误,Argument of type 'number' is not assignable to parameter of type 'Length' */
printLength('1111') /* 正确 */
3.三元表达式
type TypeRes=Type1 extends Type2? Type3: Type4;
这里表达的意思就是如果类型Type1可被分配给类型Type2,则类型TypeRes取Type3,否则取Type4。那这里的关键是怎么判断type1可被分配给类型Type2。
我们可以这样理解:类型为Type1的值可被赋值给类型为Type2的变量。可以具体分为一下几种情况:
Type1和Type2为同一种类型
type Type1=string;
type Type2=Type1;
type Type3=number;
type Type4=string;
type TypeRes=Type1 extends Type2? Type3: Type4 //number类型
let a:TypeRes=6666
Type1和type2是同一类型
Type1是Type2的子类型
class Animal {
public name;
constructor(name: string) {
this.name = name;
}
}
class Sheep extends Animal {
constructor(name: string) {
super(name);
}
}
type Type1=Sheep;
type Type2=Animal;
type Type3=string;
type Type4=number;
type TypeRes=Type1 extends Type2? Type3: Type4; //string
let a:TypeRes='6666'
Type1通过extends继承了Type2
Type2类型兼容类型Type1
type Type1={
name:string;
age:number;
gender:string;
}
type Type2={
name:string;
age:number;
}
type Type3=string;
type Type4=number;
type TypeRes=Type1 extends Type2? Type3: Type4; //string
let a:TypeRes='1111'
由于类型Type1拥有至少与Type2相同的属性,因此Type2是兼容Type1的。也就是说Type1类型的值可被赋值给类型为Type2的变量。
infer:用于条件中的类型推导
假如想在获取数组里的元素类型,在不会infer之前我是这样做的,我们需要列举出每个类型
type Ids = number[];
type Names = string[];
type SelectType<T> = T extends Names ? string : T extends Ids ? number : T;
type TypeId = SelectType<Ids>; // idType 类型为 number
type TypeName = SelectType<Names>; // nameType 类型为string
let personId:TypeId=111
let personName:TypeName='Mike'
可以通过infer把上面的代码改成
type SelectType<T> = T extends Array<infer E> ? E:T
type TypeId = SelectType<number[]>; // idType 类型为 number
type TypeName = SelectType<string[]>; // nameType 类型为string
type TypeBoolean = SelectType<boolean[]>; // nameType 类型为boolean
let personId:TypeId=111
let personName:TypeName='Mike'
let isPerson:TypeBoolean=true
这样代码是不是简单多了,而且不用一个个去写数组里的元素类型,还有几种在其它类型里面的使用
对象推导
type Obj<T> = T extends { a: infer U } ? U : never;
type T1 = Obj<{ a: string }>; // T1类型为 string
type T2 = Obj<{ a: number }>; // T1类型为 string
let a:T1='11'
let b:T2=2112
2.推导出联合类型
type Obj<T> = T extends { a: infer U; b: infer U } ? U : never;
type T1 = Obj<{ a: string; b: number }>; // T11类型为 string | number
let a:T1='111'
let b:T1=333
推导出交叉类型
type T1 = {name: string};
type T2 = {age: number};
type Obj<T> = T extends {a: (x: infer U) => void, b: (x: infer U) => void} ? U : never;
interface Props {
a: (x: T1) => void;
b: (x: T2) => void;
}
type T3 = Obj<Props>
let obj:T3={
name:'1111',
age:66666
}
Required:所有可选属性变成必选
interface Person {
name?:string,
age?:number
}
// 不会报错,Required<Person>等于
// interface Person {
// name:string,
// age:number
// }
let Person:Required<Person>={
name:'Mike',
age:11
}
Partial:所有属性变成可选
interface Person {
name:string,
age:number
}
// 不会报错,Partial<Person>等于
// interface Person {
// name?:string,
// age?:number
// }
let Person:Partial<Person>={
name:'Mike'
}
Exclude:从已有类型中提取汇总一些类型,Extract<T,U>从 T 中提取出 U
type A=string|number|boolean
type B=Exclude<A,string> /* 等价于 number|boolean */
Extract:和 Exclude 相反,Extract<T,U>从 T 中提取出 U
type A=string|number|boolean
type B=Extract<A,string|number> /* 等价于 string|number */
Record:Record<K, T> 的作用是将 K 中所有的属性的值转化为 T 类型
type A='key1'|'key2'
type B=Record<A,string>
let b:B={
key1:'11',
key2:'22',
}
Readonly:把有属性值转换为只读的
type A={
age:number,
name:string
}
let a:Readonly<A>={
age:11,
name:'Mike'
}
// 只读属性,赋值会错误
a.age=4343 /* 报错 */
Pick:从某个类型中挑出一些属性出来
type A={
age:number,
name:string,
height:number
}
type B=Pick<A,'age'|'name'> * 等价于 { age:number,name:string,} */
let b:B={
age:11,
name:'Mike'
}
Omit:与Pick相反,Omit<T,K>从T中取出除去K的其他所有属性
type A={
age:number,
name:string,
height:number
}
type B=Omit<A,'age'|'name'> /* 等价于 {height:number} */
let b:B={
height:20
}
NonNullable:去除类型中的null和undefined
type A = NonNullable<string | number | undefined>; // string | number
type B = NonNullable<string[] | null | undefined>; // string[]
ReturnType:用来得到一个函数的返回值类型
type A= ()=>string
type B=ReturnType<A>
let b:B='11111'
Parameters:用于获得函数的参数类型所组成的元组类型
type A= (a1:string,a2:number,a3:boolean)=>string
type B=Parameters<A> /* 等于[string,number,boolean] */
let b:B=['111',2,true]
InstanceType:返回构造函数类型T的实例类型
class People {
name: string
age: number
constructor(name: string) {
this.name = name;
}
}
type IType = InstanceType<typeof People>
// type IType = People
// 因为constructor默认返回this
// constructor People(name: string): People
联合类型
表示多个类型中的一个
type A=string|number
let a:A=111
let a1:A='222'
交叉类型
将多个类型合并为一个类型
type A ={
a:number
}
type B={
b:string
}
type C=A&B
let c:C={
a:1111,
b:'222'
}
类型断言
类型断言可以手动指定一个值的类型,允许你覆盖Typescript的推断,以你任何你想要的方式分析它。类型断言有俩种写法:as关键字或者尖括号<type>表示
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'
正常写这段代码会异常,但我们如果改写成下面这种写法,编译器正常了
interface Foo{
bar:number,
bas:string
}
// as写法
const foo = {} as Foo
// 尖括号写法
// const foo=<Foo>{}
foo.bar = 123;
foo.bas = 'hello';
注意:类型断言只是欺骗编译器,让编译器可以编译通过
非空断言
一般都是使用!叹号表示,x!的值不会为 null 或 undefined
let user: string | null | undefined;
console.log(user!.toUpperCase()); // 正确
console.log(user.toUpperCase()); // 错误
声明文件
我们使用第三方库时,因为第三方库可能是Typescript编译的js或是纯js编写的,那这时我们引入并使用第三方库的时候,就失去了Typescript的校验,所以这时候我们就需要声明文件,才能获得对应的代码补全、接口提示等功能,声明文件一般放在.d.ts文件文件
当我们这样引用Jquery的时候,在没有声明文件的时候,Typescript会提示异常
$('#foo'); //ERROR: Cannot find name 'jQuery'
接下来我们怎么讲下声明文件创建的方式
自己书写声明文件
我们可以建立专门专门存放的文件,.d.ts文件的命名一般和ts文件名字一样,比如我们刚刚举例的Jquery,我们可以这样编写。
//jQuery.d.ts
declare let jQuery: (selector: string) => any;
这样我们再次使用的插件方法的时候,编译系统会使用我们编写的.d.ts文件中寻找相应的校验文件
通过Typescript的编译配置,自动生成声明文件
我们可以在tsconfig.js这样配置
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "./dist/",
"declaration": true, // 生成相应的 '.d.ts' 文件
"declarationDir":"./dist/types" //声明文件放在统一目录下存放
}
}
我们可以看下下面的文件结构,大致是这样子的
然后在package.json文件这样配置,在
types 或 typings 字段指定一个类型声明文件地址
"main": "./dist/index.js",
"types":"./dist/types/index.d.ts"
这样别人使用该库的时候,Typescript就可以自动找到相应的声明文件校验。
通过社区成熟的第三方声明文件库引入
声明文件库可以去DefinitelyTyped寻找相应的包,这个一般统一放在node_moudles的@types文件目录下
上面的文件都是声明文件,有些是引入npm包依赖的,有些是我们手动下载引入的
引入第三方声明库文件或我们在写库的时候手动生成是常用的方式,手动编写声明文件是最难的,需要我们熟练的掌握Typescript语法,而且也需要花费更多时间在这方面的代码上,需要慎重的思考。
后语
这次分享就到这里了,当然这里没有全部涵盖Typescript的知识点,但应对我们的项目日常开发足够了,辛苦啦!!!可以看到最后。
最后提醒一句,知识并不是放进收藏夹就会增长,而是通过反复学习一点点进步的,加油!!!
参考资料
- Typescript官方文档:www.tslang.cn/docs/home.h…
- extends详解:www.45fan.com/article.php…