TypeScript超详细教程

2020-12-23  本文已影响0人  十年之后_b94a

前言

TypeScriptJavaScript 的一个超集,支持 ECMAScript 6 标准
可以理解成TypeScript是高版本的JS
中文学习网址

安装TypeScript

npm install -g typescript

编写第一个TS

注意此时文件的后缀不再是js而是ts

function hello(){
  let str:string = "Hello Word!!";
  console.log(str)
}
hello();

然后执行命令

tsc ***.ts

执行结束后 会转为js文件 然后我们在执行js文件
这一系列操作下来太麻烦了,我们直接一步到位

安装便捷编译

npm i ts-node -g

执行当前命令会直接输出

ts-node ***.ts

定义变量

在ts中一旦定义了一个变量可以指定一个类型。
基础静态类型:
number、string、void、null、undefined、boolean、symbol、、、
一旦定义了类型,那么在重新赋值必须是和第一次赋值一样的类型,即使你在定义变量的时候不指定类型

let a : number = 1;
let b : string = "1";
b = 1;//报错字符串数字和number类型数字也是不能直接赋值的
let c = false;//在ts中这也是可法的,在定义的时候你可以不用指定类型
c = "false";//报错因为当前c变量在第一次赋值是属于boolean类型你不可改变该类型
c = true;//可法
c = 0;//不可法
c = !0;//可法,你可以赋值一个逻辑运算

基础对象类型定义

let object : {
  name : string,
  age : number
} = {
  name : "测试基础对象定义",
  age : 1
}
object = {
  name :"减少、增加属性",
  age : 222,
  adress : "测试"
}//报错

基础数组类型定义

let any : string [];
any = ["a","b","c"]
any = ["1","2",3]//报错
let any1 :number[] = [1,2,3];

复杂多类型数组定义

let any :(number|string)[] = [1,"a","b",2]

利用接口interface定义数组

//其实一个普通的数组转为对象
let a = [1,2,3];
其实他的对象无法类似就是{0:1,1:2,2:3}
interface List{
  [index:number]:number;//这里的index指向的是数组下标
}
let list : List = [1,2,3]

定义函数

//定义一个没有返回值的函数
let doSomething : ()=>void=()=>{};
//返回字符串的函数
let doSomething1 : ()=>string = ()=>"1"
//返回数字类型的函数
let doSomething2 : ()=>number= ()=>1
//返回一个对象
let doSomething2 :()=>Person = ()=>new Person();

定义一个自定义类型
关键字inteface

interface Person { 
  userName : string,
  age : number
}
let xiaoming : Person = {
  userName :"小明",
  age : 20
}

可选参数、多传参数定义

//可选参数
function test(a:string,b?:string):void{};
function test1(a:string){}
test1("a","b","c");//会报错
function test1(a:string,...args:string[]){}

函数的返回类型注解

//返回值类型注明是string
function doSomething(name:string):string{
  return "你好啊" + name;
}
//没有返回值
//或者直接可以不写void
function doSomething(name:string):void{
  return "你好啊" + name;
}
let doSomething3 : Function = ():string=>"1" 

函数返回类型有一个特殊的类型never
那么什么是never
如果某个函数是never的返回类型,那么代表这个函数被阻断了,不能完全执行完成。

let neverFn = ():never=>{
  console.log(111);
  while(true){}//死循环
  console.log(222);//不能执行到这里
}
let neverFn = ():never=>{
  console.log(111);
  throw new Error();
  console.log(222);//不能执行到这里
}

元祖

元祖的定义其实就和数组差不多

let any : [string,string,number] = ["a","b",1]

粗看感觉和普通的多类型数组定义差不多,但是现在类型枚举在括号内,而且每一项必须对应,不能少也不能多,也就是第一项你定义了string类型,那么你数据的第一项必须是string类型,而且有多少值你得定义多少类型,看起来很鸡肋

接口

interface 关键字定义一个接口

基础定义接口

interface Person{
    name:string;
    age:number;
    say:()=>void
}

let xiaoming:Person = {
    name:"小明",
    age:21,
    say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());

任意类型any

interface Person{
    name:string;
    age:number;
    like:any;
    say:()=>void
}

let xiaoming:Person = {
    name:"小明",
    age:21,
    like:"游泳",
    say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());

可有可无属性?

interface Person{
    name:string;
    age:number;
    like:any;
    say:()=>void;
    desc?:any;
}

let xiaoming:Person = {
    name:"小明",
    age:21,
    like:"游泳",
    say:()=>`HI I am xiaoming`
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());

联合属性 以|分割属性

interface Person{
    name:string;
    age:number;
    like:any;
    say:()=>void;
    desc?:any;
    adress:string|string[]|(()=>string);
}

let xiaoming:Person = {
    name:"小明",
    age:21,
    like:"游泳",
    say:()=>`HI I am xiaoming`,
    adress:"艾欧尼亚"
}
console.log(xiaoming.name);
console.log(xiaoming.age);
console.log(xiaoming.say());

类的接口定义

interface Person{
    name:string;
    age:number;
    like:any;
    say:()=>void;
    adress:string|string[]|(()=>string);
}

let xiaoming:Person = {
    name:"小明",
    age:21,
    like:"游泳",
    say:()=>`HI I am xiaoming`,
    adress:"艾欧尼亚"
}

class XiaoHong implements Person{
    name ="小红";
    age = 20;
    say=()=>"哈哈哈";
    adress="艾欧尼亚";
    like = "玩游戏";
}

接口与接口之间的继承extends

interface Person{
    name:string;
    age:number;
    like:any;
    say:()=>void;
    adress:string|string[]|(()=>string);
}

interface Teach extends Person{
    job:string;
}

let wangTeach:Teach = {
    name:"袁华",
    age:45,
    like:"秋雅",
    say:()=>console.log("这道题我不会做"),
    adress:"夏诺特烦恼",
    job:"装男人"
}

类的定义

java中一样 也有关键字:
1、private私有的
2、protected保护的
3、public公开的

public的使用默认属性都会添加

interface Info{
    name:string,
    age:number
}
class Person{
    public name:string;
    age:number;//默认就是public
    constructor(info:Info){
        console.log(info);
        this.age = info.age;
    }
}
let xm = new Person({name:"a",age:18});
xm.name="小明";//可以直接修改
console.log(xm);

如果某个属性或者方法标注的是public;那么该类被实例化后,可以直接修改属性的值,也可以被继承


private不可被子类/实例修改值不可被继承
只能在类的内部被使用

class Person{
    public name:string;
    private money:number;
    age:number;//默认就是public
}

class Teach extends Person{
    constructor(name:string){
        super();
        this.name = name;
        this.age = 45;
        this.money = 200000;//报错
    }
}

let wangTeach = new Teach("王老师");
console.log(wangTeach.money);//报错

虽然private的属性不能被修改或者读取,但我们可以包装get和set方法

class Person{
    constructor(private _age:number){}
    get getAge(){//可以进行运算后返回
        return this._age - 10;
    }
    set setAge(age:number){
        this._age = age;
    }
}

let p = new Person(28);
//console.log(p._age);//报错
console.log(p.getAge);//注意!!!这里不是方法调用!!!
p.setAge = 32;

console.log(p.getAge);//注意!!!这里不是方法调用!!!

注意get、set调用时不在是方法而是属性的方式


protected可以被继承且能被子级修改值,但不能被外部(实例对象)修改值或访问值

interface Info{
    name:string,
    age:number
}
class Person{
    public name:string;
    private money:number;
    protected like:string[];
    age:number;//默认就是public
}

class Teach extends Person{
    constructor(name:string){
        super();
        this.name = name;
        this.age = 45;
        this.like = ["教书","看书"]
    }
}

let wangTeach = new Teach("王老师");
console.log(wangTeach.like);//报错

类的属性简化定义

class Person{
  constructor(public name:string){}//简化定义他和下面的语句等价
/*  name:string;
  constructor(name:string){this.name=name;}*/
}

如果当前类被继承那么该怎么写呢?而且子级也有一个传参构造函数

class Person{
  constructor(public name:string){}
}
class Teacher extends Person{
  constructor(public age:number){
    super("王老师");//重点
  }
}
let t = new Teacher(18);
console.log(t)

注意当父类不管有没有构造函数constructor只要子级需要使用构造函数,那么super必须被调用

什么是super:可以理解成调用父类的构造函数方法

类的静态属性

在属性或者方法前加上static修饰词,然后使用类名.属性/方法名调用,类似json

class Persons{
    static userName:string = "小明";
    static sayHello(){
        console.log("你好啊"+this.userName);
    }
}
console.log();
Persons.sayHello();
console.log(Persons.userName);

类的只读属性

修饰词:readonly


 class Person{
     constructor(public readonly name:string){}
 }

 let p = new Person("小明");
 console.log(p.name);
 p.name = "测试"//报错
 console.log(p.name);

抽象类的使用

修饰词abstract定义的类如果被继承那么子类必须实现被abstract修饰的属性/方法

abstract class Person{
    abstract say();
    abstract name:string;
}

class Student extends Person{
    name = "小明";
    say() {
        console.log("我是学生");
    }
}

class Teacher extends Person{
    name = "王老师";
    say(){
        console.log("我是"+this.name);
        
    }
}

tsconfig.json文件

我们知道ts文件最终会被编译成浏览器认识的js文件,那么编译过程我们可以根据我们的配置进行编译,如压缩、去掉注释等配置。
具体我们可以去查看typescript中文网,或者你可以查看相关博客

tsc -init //生成tsconfig.json文件
"include":[],只编译定义的文件
"exclude":[],排除定义的文件进行编译
"files":[]和include意义类似
"compilerOptions.removeComments":是否删除注释
"compilerOptions.strict":js严格模式
"compilerOptions.outDir":编译后输出到哪里一半我们设置为./build
"compilerOptions.rootDir":编译的跟文件路劲
"compilerOptions.sourceMap":生成map文件,改文件的用途是方便在编译后如果调试能够快速找到源文件(ts)出错的位置
"compilerOptions.noUnusedLocals":定义的变量没有被调用则不会被编译,非常有用
"compilerOptions.noUnusedParameters":定义的方法没有被调用则不会被编译,非常有用
、、、、

联合类型和类型保护

什么是联合类型?当一个变量或者形参的类型是多个的时候,使用|作为多类型注解;
例如 const anys:()=>string|string|number|boolean;
什么是类型保护?当一个形参或者变量是联合类型时,我们不确定具体是那种类型,那我们就使用类型保护

interface Student{
    study:boolean;
    says:Function;
}

interface Teacher{
    name:string;
    lecture : ()=>{};
}

function run(animal:Student | Teacher){
    //animal.lecture();//会直接报错,因为animal的类型是两种不确定实参到底是哪个所以我们要类型保护
    if(animal.name){
        (animal as Teacher).lecture();//类型保护
    }else{
        (animal as Student).says();
    }
}
function run2(animal:Student | Teacher){
    //animal.lecture();//会直接报错,因为animal的类型是两种不确定实参到底是哪个所以我们要类型保护
    if("name" in animal){//使用in
        (animal as Teacher).lecture();
    }else{
        (animal as Student).says();
    }
    
}

枚举Enum

枚举值有一个固定的下标将其定义,当然我们也可以改变每个对应的下标

 enum Status {
     MESSAGE,TEST,MORE,OTHER
 }
 console.log(Status.MESSAGE);//0
 console.log(Status.TEST);//1
 console.log(Status.MORE);//2
 console.log(Status.OTHER);//3

我们也可以只给其实枚举值一个下标,那么其他的对应下标会依次递加

 enum Status {
     MESSAGE=5,TEST=9,MORE=10,OTHER=23
 }
 console.log(Status.MESSAGE);
 console.log(Status.TEST);
 console.log(Status.MORE);
 console.log(Status.OTHER);

我们也可以根据下标反推对应的枚举是什么

enum Status {
    MESSAGE,TEST,MORE,OTHER
}

console.log(Status[0]);
console.log(Status[1]);
console.log(Status[2]);
console.log(Status[3]);
console.log(Status[4]);//undefined

泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。


function testq<T>(argus:T):T{
    return argus;
}
//多个泛型
function testqa<T,P>(a:T,b:P){
    return `${a}${b}`;
}
 function test<T>(argus:T[]){
    return argus.join(",")
 }

 console.log(test<String>(["wq","xaa","csq"]));

类中使用泛型

class Test<T>{
    constructor(private names:T[]){}
    getName(index:number):T{
        return this.names[index];
    }
}

泛型的继承

interface Grils{
    name:string;
}

class SomeSrils<T extends Grils>{
    constructor(private names:T[]){}
    getName(index:number):string{
        return this.names[index].name;
    }
}

let s = new SomeSrils([{name:"小花"},{name:"小名"},{name:"小兰"}]);
console.log(s.getName(2));

泛型的类型约束

可以理解为泛型只能为所提供的的类型中一个

//该泛型必须是number/string
class SomeSrilsy<T extends number | string>{
    constructor(private names:T[]){}
    getName(index:number):T{
        return this.names[index];
    }
}

let sy = new SomeSrilsy([1,"1","s"]);
console.log(sy.getName(2));

命名空间

命名空间可以有效防止全局变量被污染
namespace和``

namespace Module{
    class Person{
        constructor(public name:string){}
    }
    class Teach{
        constructor(public name:string){}
    }
    export class Student{
        constructor(public name:string){}
    }
    export class Main{
        constructor(){
            new Person("小明");
            new Teach("小话");
            new Student("小兰");
        }
    }
}
new Module.Main();

使用tsconfig文件防止多个模块都被编译成多个文件。

什么意思呢?比如一个项目中有三个模块,但是我们跟文件index.html,只引入了打包后的main.js。但是我们的main.js又引入了其他三个模块组成的

//tsconfig.js
"compilerOptions.outFile":"./build/main.js"打包后的唯一文件名
"compilerOptions.module":"amd"

ts与jq一起使用

我们知道jq$会在ts中报错

解决方法一

直接声明$

declare var $:any;

解决方法二

下载@types/jquery

npm i @types/jquery -D

解决方法三

创建.d.ts文件对jq进行声明

上一篇下一篇

猜你喜欢

热点阅读