“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情”
一、TypeScript简介
-
TypeScript是一种由微软开发的一门编程语言,它实际上是js语言的一种扩展(JS的超集),扩展了更多大语言(面向对象语言)所特有的语法。
-
TypeScript支持所有JavaScript语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作。TypeScript是为 大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。
-
可以在任何浏览器,任何计算机和任何操作系统上运行,并且是开源的。本课程介绍了TypeScript的基本概念、常用语法和高级特性。
-
官方网站:www.typescriptlang.org。
二、安装和编译
安装TypeScript(目的是使用全局命令tsc,把ts文件编译成js文件)
yarn global add typescript
编译(compile)
tsc 文件名.ts
三、类型注解
即对变量或者函数的类型进行解释,就是类型注解,其目的是约束变量或者函数的结构。
基本类型
TypeScript中的基本类型都可以在这里用作类型约束,语法为let | const | var + 变量名:变量类型 = 值
在这里值
的类型必须符合变量类型
所指定的类型 ,否则会报错。
/*
var | let | const + 变量名:变量类型 = 值
变量类型:
string/number/boolean/null/undefined/symbol/object
TypeScript中额外的变量类型:
1. void === null | undefined
2. any 表示任意类型
*/
let name:string = "张三";
name = 123;//报错
引用类型
-
只要求是引用类型即可。如果只是要求变量的类型是个引用类型,具体是数组还是json对象或者是函数,我们并不关心,则可以使用js中的
object
来进行类型约束,但是这种方式用的很少,不太使用。let o:object = []; o = {}; o = ""; //报错
-
必须是数组
let arr:string[] = ["a","b","c"]; let arr1:number[] = [1,2,3];
-
必须是函数
let func:Function = ()=>{}; //这种方式不能精确的约束函数参数的类型和返回值的类型 let func:(a:number,b:number)=>number = (a,b)=>a+b; //这种方式则约束了函数接受的两个参数a和b以及返回值的类型都必须为number类型
-
必须是json对象
let json:{name:string,sex?:string} = {name:"张三"}; //如果json对象的某个属性为函数怎么处理 (?表示该属性可选) let json1:{name:string,sex?:string,func:(a:number,b:number)=>number} = { name:"张三", func:(a,b)=>a + b, } json1.func(1,2); //在调用函数的时候也必须传递两个类型为number类型的,否则会报错 //如果这个json对象有任意多的属性怎么办 let json2:{a:number,b:string,[propName:string]:string | number} = { a:1, b:"s", aaa:"", //..其他属性可以任意写,只要类型为number或者string都可以 }
四、类型别名
类型别名的作用是:⾃定义类型(起作用是约束变量或者函数的结构),如果有很多个变量,他们的结构都是相同的,则我们可以把约束这些变量结构的类型起一个别名,然后方便我们使用。
//类型别名 type + 类型别名(首字母大写)= 类型约束条件
type Obj = {name?:string,sex:string,age:number,cls:string,func?:(a:number,b:number)=>number};
var myObj:Obj = {
// name:"",
sex:"男",
age:10,
cls:"06",
}
var myObj2:Obj= {
name:"张三",
sex:"男",
age:20,
cls:"09",
}
var myObj3:Obj = {
name:"李四",
sex:"男",
age:20,
cls:"09",
}
我们可以在一个单独的文件中集中管理类型别名,然后导出接口供其他文件使用
//export导出类型别名
export type Obj = {name?:string,sex:string,age:number,cls:string,func?:(a:number,b:number)=>number};
//使用
import {Obj} from 路径
注意:类型别名首字母大写
五、接⼝(Interface)
接口的定义和使用
接⼝仅约束变量或者函数的结构,不要求实现,使⽤更简单。
// Person接⼝定义了结构(即将来凡是使用该接口约束的对象,只能写成这样)
interface Obj{
name?:string,
sex:string,
age:number,
cls:string,
func?:(a:number,b:number)=>number
};
let O:Obj = {
sex:"男",
age:20,
cls:"07",
func:(a,b)=>a + b,
}
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,如下代码就会报错👇。
```
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
//number不是string的子集,会报错
```
我们可以在一个单独的文件中集中管理接口,然后导出接口供其他文件使用。
// export 导出接口
export interface Person {
name?:string,
sex:string,
age:number,
cls:string,
func?:(a:number,b:number)=>number
}
注意:接口首字母大写
接口的继承
- 接口继承接口
interface A {
name:string,
sex:string
}
interface B extends A{
age:number
}
let a:B = {
name:"张三",
sex:"男",
age:20
}
- 接口继承类(常见的面向对象语言中,接口是不能继承类的)
接口继承类其实就是接口继承接口,因为ts中在创建一个类的时候,同时也会创建一个对应的接口,这个接口是对当前类的实例对象结构的一个描述(包括实例属性、方法以及原型方法),例如:
//现在创建一个Sum类
class Sum{
a:number;
b:number;
constructor(a:number,b:number){
this.a = a;
this.b = b;
}
getTotal(){
return this.a + this.b;
}
}
//同时ts在编译的时候也会在内存中创建一个对应的接口,来描述该类的实例对象的结构,相当于向下面一样创建一个接口
interface Sum{//这个接口看不到,但是确存在着
a:number,
b:number,
getTotal():number,
}
//所以下面我另外写一个接口,它可以直接继承Sum类(要记住:其实继承的是Sum类对应的接口)
interface OtherInterface extends Sum{
name:string,
}
类型别名和接口的区别
接口能够继承、但是类型别名不可以。
六、函数
声明式函数约束
- 直接在函数内部,对函数参数和返回值进行类型约束,可选参数在后面加上
?
即可。
function threeSum(a:number,b:number=20,c?:number):number{
return a + b + c;
}
threeSum(1,2,3)
- 注意:可选参数只能写在参数列表的最后,并且一个已经设置为可选的参数,不能同时又设置默认值。
函数表达式约束
let threeSum = function (a:number, b:number, c:number):number {
return a + b + c;
}
以上代码编译是能都通过的,但是,上面的写法只是对等号右边的匿名函数做了类型约束,等号左边的threeSum的类型是ts自己类型推断出来的,如果我们想直接对threeSum做类型约束,需要像下面这样写👇。
let threeSum: (a: number, b: number, c: number) => number = function (a, b, c) {
return a + b + c;
}
//(a: number, b: number, c: number) => number这段代码,不是函数的具体实现,只是一个类型而已,对threeSum这个//变量起到类型约束的作用
利用类型别名对函数进行类型约束
//该类型别名ThreeSum表示即将被约束的函数应该长这样:接受两个参数,他们的类型都是number,并且返回值也是number类型的
type ThreeSum =(a: number, b: number, c: number) =>number;
const threeSum:ThreeSum = function (a, b, c) {
return a + b + c;
}
利用接口对函数进行类型约束
//该类接口ThreeSum表示即将被约束的函数应该长这样:接受两个参数,他们的类型都是number,并且返回值也是number类型的
interface ThreeSum{
(a:number,b:number,c:number):number,//如果没有函数名字,则表示被该接口约束的变量本身就是一个函数,而不是一个对象,对象下面的某个属性是函数
}
const threeSum:ThreeSum = function (a, b, c) {
return a + b + c;
}
函数重载(了解)
当你需要用同一个函数,但是,该函数接受的参数个数,或者参数类型,或返回值类型不同时,就可以利用函数重载来实现该函数的类型约束。重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
比如现在我要写这么一个函数,接受两个参数a,b,要求a,b必须同时为string或者number。返回值类型必须和参数类型一致
function towSum(a,b){
return a + b;
}
//可能想到的解决方案--利用泛型
function<T>(a:T,b:T):T{
return a + b;
}
//但是这样ts会报错,因为这里T可以是任意的类型,如果传递的是一个对象,则对他们使用加法,则是不合适的
//正解:利用函数重载
function towSum(a: number, b: number): number;
function towSum(a: string, b: string): string;
function towSum(a, b) {
return a + b;
}
/*
上例中前两次都是对函数类型(参数和返回值的)的约束,最后一次是函数实现
*/
- 注意:为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
- 注意,
function sum(a:any, b:any): string | number
并不是重载列表的一部分,因此这里只有两个重载:一个是接收数字,另一个接收字符串。 以其它参数调用sum
会产生错误。另外定义函数时的返回值类型必须包含函数重载时的返回值,否则也会报错。
七、类型之间的运算
就像js变量之间可以运算一样,在ts中类型之间(包括内置类型以及类型别名和接口之间)也是可以运算的,只是运算的方式没有那么多。
或运算(|)
或运算后产生的类型我们称之为联合类型。
//a的类型可能是number也可能是string,则可以做如下处理
let a:number | string = "字符串";
a = 123;
//变量o的结构可能是Alias1或者是Alias2或者是I
type Alias1 = {name:string}
type Alias2 = {sex:string}
interface I{
age:number
}
type Alias3 = Alias1 | Alias2 | I;
let o:Alias3 = {
name:"张三",
sex:"男",
age:20,
}
与运算(&)
与运算后产生的类型我们称之为交叉类型,注意交叉类型只能用于引用类型,基本类型不能使用。
例如:一个变量不可能即是string又是number类型的👇。
type Obj1 = {name:string};
type Obj2 = {age:number}
type Obj = Obj1 & Obj2;
const myObj:Obj = {//这是myObj的类型被Obj1和Obj2同时约束 ,所以必须同时具有name和age两个属性
name:"张三",
age:20
}
八、类的约束
- ts中的类和es6中⼤体相同,这⾥重点关注ts带来的访问控制等特性
class Parent {
static staticProp = "somevalue";//静态属性
sex:string;
private _foo:string = "foo"; // 私有属性,不能在类的外部访问
protected bar = "bar"; // 保护属性,可以在⼦类中访问
// 参数属性:构造函数参数加修饰符,能够定义为成员属性
constructor(public name = "张三") {
}
// ⽅法也有修饰符
private someMethod(a:string,b:string):void {}
// 存取器:属性⽅式访问,可添加额外逻辑,控制读写性
get foo() {
return this._foo;
}
set foo(val) {
this._foo = val;
}
}
通过抽象类(Abstract Class)来约束子类
抽象类是供其他类继承的基类,抽象类不允许被实例化,抽象类中的抽象方法和抽象属性必须在子类中被实现。
如何定义抽象类?---在class前面加上关键字abstract 即可。
abstract class Sum {
abstract c:number;//这是一个抽象属性,必须被其子类实现
a:number;
b:number;
constructor(a:number, b: number) {
this.a = a;
this.b = b;
}
abstract getTotal():number;//这是一个抽象方法,必须被子类实现
}
class MySum extends Sum {
c = 100;//实现抽象类中的抽象实例
a:number;
b:number;
constructor(a: number, b: number) {
super(a,b);
}
getTotal(){//实现抽象类中的抽象方法
return this.a + this.b;
}
}
const sum = new MySum(1, 2);
sum.getTotal();
通过接口来约束类(类实现--implements接口)
假设现在有一个需求:有n个类,他们的实例对象都需要有一个sayName方法和name属性,如何用ts去约束他们的类型呢?
这时我们可以通过interface定义接口,然后用过implements
(翻译过来是实现的意思)关键字去实现对类结构的约束(注意只能去约束类的实例对象上的属性和方法),代码如下👇。
interface Test{
sayName():void,
name:string,
}
interface Test2{
sayName2():void,
name2:string,
}
class Hello implements Test{//Hello类必须实现Test接口所规定的方法和属性(注意是实例属性和实例方法)
name = "张三"
sayName(){}
}
//一个类可以同时实现过个接口
class Hello2 implements Test,Test2{
//Hello2类必须同时实现Test接口和Test2接口所规定的方法和属性(注意是实例属性和实例方法)
name = "张三";
name2 = "李四";
sayName(){},
sayName2(){}
}
九、泛型
泛型(Generics)是指在定义函数、接⼝或类的时候,不预先指定具体的类型,⽽在使⽤的时候再指定。
类型的⼀种特性。以此增加代码通用性。类似于函数的形参,这里其实指的是类型参数(或者是类型变量)。
函数中使用泛型
// 假设我们现在有一个需求:函数参数的类型必须并不确定,需要在执行的时候确定,现在我们就可以用泛型
function test<T>(a:T,b:T){
//...你的代码
}
test<number>(1,2);//这时类型变量T指的是number
test<string>('张','三');//这时类型变量T指的是string
接口或者类型别名中使用泛型
interface Test<T,S>{//两个类型参数T和S(形参)
foo:T,
bar:S
log():T,
}
const obj:Test<string,number> = {//传递类型参数 string和number (实参)
foo:"foo",
bar:123,
log(){
return 'foo'
}
}
//类型别名也可以使用泛型
type Test<T,S> = {
foo:T,
bar:S
log():T,
}
const obj:Test<string,number> = {
foo:"foo",
bar:123,
log(){
return 'foo'
}
}
在类中使用泛型
class Hello<T,S>{//两个类型参数T和S,在Hello类的内部可以用T和S对实例属性和方法进行类型约束
foo:T;
bar:S;
constructor(foo:T,bar:S){
this.foo = foo;
this.bar = bar;
}
//类中sum方法的重载
sum(a:string,b:string):string;
sum(a:number,b:number):number;
sum(a:any,b:any){
return a + b;
}
handleFoo():T{//用T约束方法handleFoo的返回值类型
console.log('this.foo',this.foo);
return this.foo;
}
}
const hello = new Hello<string,any[]>("foo",[]);//可以显示的指定类型实参string和any[]
const hello1 = new Hello("foo",[]);//也可以不用显示的指定类型实参,ts会自己进行类型推断,从而判断出T和S的类型
十、类型断言(了解)
类型断言的意思就是,我们认为的告诉ts,该标识符是个什么类型,而不是有ts自己去判断。通过as关键字可以实现类型断言。
//在ts中直接这样为window的某个属性赋值会报错,提示Window类型中不存在foo属性
window.foo = "foo";
//如何解决---断言
(window as any).foo = "foo"
//这里的意思是我们把window的类型断言为any,而一个any类型的变量,可以访问改变量下面的任何属性
十一、TypeScript配置文件tsconfig.json(了解)
改文件一般放在项目的根目录。
{
"compilerOptions":{//编译ts时的配置选项
"outDir":"./dist",//目标输出路径
"noEmitOnError":true, // 编译的源文件中存在错误的时候不再输出编译结果文件,默认为false
"noImplicitAny":true,//控制当源文件中存在隐式的any的时候是否报错,默认false,不会报错
"noImplicitThis": true, // 当源文件中存在this为any的时候报错,默认为false不会报错
"target": "es5",//生成的js符合什么版本的js规范,其默认值为es3
"removeComments": true, // 是否在输出文件中清除源文件中的注释,默认false不删除
"lib":["ES2015","DOM","DOM.Iterable"],//这个是用于指定要引入的库文件,属性值为一个数组,有es5、es6、es7、dom四个值可选,如果不配置lib,那么其默认会引入dom库,但是如果配置了lib,那么就只会引入指定的库了,
"module":"CommonJS",//这个用于指定要使用的模块标准,如果不显式配置module,那么其值与target的配置有关,其默认值为target === "es3" or "es5" ?"commonjs" : "es6",所以当target为es3或者es5的时候,module的默认值为commonjs,当target为其他的值的时候,那么module的默认值为es6
"moduleResolution":"Node",//配置模块解析规则
"typeRoots":[//这个用于指定类型声明文件的查找路径。默认值为node_modules/@types,即在node_modules下的@types里面查找,需要注意的是这里仅仅是d.ts文件的查找路径
"node_modules/@types",//默认值
],
"jsx": "react", // 在 .tsx文件里支持JSX
},
"files": ["index.ts"],//由于默认情况下,tsc会编译当前项目下的所有ts文件,所以如果我们可以通过files配置来指定编译的入口文件,files的属性值为一个数组,可以指定要编译的具体文件,但是其不能使用通配符进行指定
"includes":["src","index.ts","module1.ts"],//正是由于files配置无法使用通配符进行配置,所以添加了includes配置,includes配置属性值也是一个数组,但是includes中可以使用通配符,并且可以和files一起使用,最终编译的源文件包含,files和includes的合集
"exclude": ["index.ts"],//我们可以通过excludes配置来排除掉includes配置中包含的源文件,需要特别注意的是,excludes只对includes中包含文件起到排除的作用,其无法排除files中配置的源文件
}
十二、声明文件(了解)
使⽤ts开发时如果要使⽤第三⽅js库的同时还想利⽤ts诸如类型检查等特性就需要声明⽂件,类
似 xx.d.ts。
如果不存在该js文件对应的类型声明文件,则需要我们单独下载。
yarn add @types/xxx
- 当然有些模块可能没有现成的类型文件,也就是说当前模块可能不支持ts,这时候直接用js即可,因为ts包含js,是js的超集。
互相学习,欢迎批评和建议!!!