美文网首页
TypeScript入门教程

TypeScript入门教程

作者: kim_jin | 来源:发表于2019-02-25 17:52 被阅读0次

    关于TypeScript

    TypeScriptJavaScript的超集,主要提供类型系统和对ES6的支持,由Microsoft开发,第一个版本发布于2012

    TypeScript的优势
    • TypeScript增加了代码的可读性和可维护性
      • 大部分的函数函数看看类型的定义就已经知道该如何使用了
      • 可以在编译的阶段就发现了大量的错误,防止在运行中发现过多的错误
      • 增强了编辑器和IDE的功能。
    • TypeScript非常的包容
      • TypeScriptJavaScript的超集,.js文件可以直接将名字转换为.ts即可
      • 即使不显式的定义类型,也可以自动的做出类型的推论
      • 可以定义从简单到复杂的一切类型
      • 即使TypeScript编译报错,也可以生成JavaScript文件
    TypeScript的劣势
    • 需要一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师不是很熟悉的东西,而且现阶段的中文资料也不是很多
    • 短期内可能增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期进行维护的额项目。TypeScript能够减少维护的成本
    • 集成到构建流程需要一定的工作量
    • 可能和一些库结合的并不是很完美

    原始数据类型

    对于前端开发来说,主要的类型可以 分成两部分:原始数据类型和对象类型。原始数据类型包括:布尔值数值字符串nullundefined以及ES6中的新类型symbol。现在我们主要看一下这些原始值在TypeScript中的应用。

    布尔值

    布尔值是最基本的数据类型,在TypeScript中,使用Boolean定义布尔值类型:

    let isTrue:boolean = false;
    
    // 可以通过编译
    // 对于后面来说,没有特殊强调错误代码片段,就默认为编译通过了。
    

    注意一下,使用构造函数Boolean创建的对象不是布尔值:

    let createdByNewBoolean : boolean = new Boolean(1);
    
    // index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
    

    上面代码报错的原因是new Boolean()返回一个Boolean对象:

    let createdByNewBoolean : Boolean= new Boolean(1);
    

    直接调用Boolean也可以返回一个 boolean 类型:

    let createdByBoolean: boolean = Boolean(1);
    

    TypeScript中,booleanJavaScript中的基本类型,而BooleanJavaScript 中的构造函数。其他基本类型(除了 nullundefined)一样,不再赘述。

    数值

    使用nubmber定义数值类型:

    let decLiteral:number=6;
    let hexLiteral:number = 0xf00d;
    let binaryLiteral:number = 0b1010; // ES6中的二进制表示法
    let octalLiteral:number =0o744;// ES6中的八进制表示法
    let notNumber:number =NaN;
    let infinityNumber:number = Infinity;
    

    上面的编译的结果是什么:

    var decLiteral = 6;
    var hexLiteral = 0xf00d;
    var binaryLiteral = 10;
    var octalLiteral= 484;
    var notNumber = NaN;
    var infinityNumber = Infinity;
    

    其中0b10100o744ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。

    字符串

    使用string定义字符串类型:

    // 普通字符串
    let myName:string = 'kim';
    let myAge:number = 25;
    
    // 模板字符串
    let sentence:string = `Hello,my name is ${myName}.
    I'll be ${myAge + 1} years old next month`;
    

    编译结果:

    var myName = 'kim';
    var myAge = 25;
    
    // 模板字符串
    let sentence = "Hello,my name is"+myName+".\nI'will be"+(myAge+1)+"years old next month";
    
    空值

    JavaScript没有空值(void)的概念,在TypeScript中,可以用void表示没有任何返回值的函数。

    function akertName():void{
      alert('My name is kim');
    }
    

    声明一个void类型变量是没有什么用,因为我们只能将它赋值为undefinednull:

    let unusable:void = undefined
    
    Null和undefined

    TypeScript中,可以使用nullundefined来定义这两个原始数据类型:

    let u:undefined = undefined;
    let n:null = null;
    

    undefined只能赋值为undefinednull只能赋值为null。但是这二者和void还是有区别的,nullundefined是所有类型的子类型,也就是说undefined类型的变量,可以赋值给number类型的变量:

    //这样写也是没有问题的
    let num:number =undefined;
    let u: undefined;
    let num: number = u;
    

    但是void的类型并不是这样的,不能讲void类型的变量不能赋值给number类型的变量:

    let u: void;
    let num: number = u;
    
    //index.ts(2,5): error TS2322: Type 'void' is not assignable to type 'number'.
    

    任意值

    任意值(Any)用来表示允许赋值为任意类型。

    什么是任意值类型

    如果是一个普通类型,在赋值过程中改变类型是不被允许的:

    let myFavoriteNumber:string ='seven';
    myFavoriteNumber = 7;
    
    // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
    

    但如果是 any 类型,则允许被赋值为任意类型。

    let myFavoriteNumber : any ='seven';
    myFavoriteNumber = 7;
    
    任意值的属性和方法

    在任何值上访问任何属性都是允许的:

    let anyThing :any = 'hello';
    console.log(anyThing.myName);
    console.log(anyThing.myName.firstName);
    

    也允许调用任何方法:

    let anyThing : any = 'kim';
    anyThing.setName('Tom');
    anyThing.setName('Tom').sayHello();
    anyThing.myName.setFirstName('kim');
    

    可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

    未声明类型的变量

    变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

    let something;
    something = 'seven';
    something = 7;
    something.setName('kim');
    

    上面的代码等价于:

    let something:any;
    something = 'seven';
    something = 7;
    something.setName('kim');
    

    类型推论

    如果没有明确的字段,那么TypeScript会依照类型推论的规则判断出一个类型。
    下面的代码虽然没有指定类型,但是会在编译的时候报错:

    let myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    
    // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
    

    上面的代码等价于:

    let myFavoriteNumber:string  = 'seven';
    myFavoriteNumber = 7;
    
    // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
    

    但是为什么这和any类型的还是有区别的,二者的区别在于在声明的时候没有赋值的话,就会判断成any类型,在后续就不会再进行检查。

    联合类型

    联合类型表示取值可以为多种类型中的一种。
    让我们先举个小栗子:

    let myFavoriteNumber:string | number;
    myFavoriteNumber = 'seven';
    myFavoriteNumber = 7;
    

    上面的例子说明myFavoriteNumber可以是string类型或是number类型的。但是如果我们给这个值给一个布尔值,就会报错。

    let myFavoriteNumber:string | number;
    myFavoriteNumber = true;
    
    // index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
    //   Type 'boolean' is not assignable to type 'number'.
    

    联合类型使用 | 分隔每个类型。
    这里的string|number的意思是,允许myFavoriteNumber的类型是string或是number,但是不能是其他类型。

    访问联合类型的属性或方法

    TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性和方法:

    function getLength(something: string | number): number {
      return something.length;
    }
     
    // index.ts(2,20): error TS2339: Property 'length' does not exist on type 'string | number'.
    //   Property 'length' does not exist on type 'number'.
    

    上例中,length 不是stringnumber 的共有属性,所以会报错。
    访问 string 和 number 的共有属性是没问题的:

    function getString(something:string | number):string{
      return something.toString();
    }
    

    联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

    let myFavoriteNumber:string|number;
    myFavoriteNumber = 'seven';
    console.log(myFavoriteNumber.length); // 5
    myFavoriteNumber = 7;
    console.log(myFavoriteNumber.length); // 编译时报错
     
    // index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
    

    上例中,第二行的 myFavoriteNumber被推断成了 string,访问它的length属性不会报错。
    而第四行的myFavoriteNumber 被推断成了 number,访问它的 length属性时就报错了。

    对象的类型 -- 接口

    TypeScript中,我们使用接口来定义对象的类型。

    什么是接口

    在面向对象的语言中,接口是一个很重要的概念,他是对行为的抽象,而具体如何行动需要类去实现。TypeScript中的接口就是一个很灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对【对象的形状】进行描述。
    简单的例子:

    interface Person{
      name:string;
      age:number;
    }
    let kim:Person={
      name:'kim',
      age:18
    }
    

    在上面的例子中我们定义了一个接口Person,接着定义可一个变量kim,它的类型是person,这样,我们就约束了kim的形状和接口Person一致。
    接口一般首字母大写。
    接口的变量比接口少一些或是多一些属性是不允许的:

    interface Person {
      name: string;
      age: number;
    }
     
    let kim: Person = {
      name: 'kim',
    };
     
    // index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
    //   Property 'age' is missing in type '{ name: string; }'.
    
    
    interface Person {
      name: string;
      age: number;
    }
     
    let kim: Person = {
      name: 'kim,
      age: 25,
      website: 'http://kim.com',
    };
     
    // index.ts(9,3): error TS2322: Type '{ name: string; age: number; website: string; }' is not assignable to type 'Person'.
    //   Object literal may only specify known properties, and 'website' does not exist in type 'Person'.
    
    可选属性

    有的时候我们希望不要完全匹配成一个形状,那么我们可用一些可选属性:

    interface Person {
      name: string;
      age?: number;
    }
     
    let kim: Person = {
      name: 'kim',
    };
    
    let kimi: Person = {
      name: 'kimi',
      age:28
    };
    

    可选属性的意思就是这个属性的值可以是不存在的,但是在这种情况下依旧不允许添加未定义的属性,我们在下面举个栗子:

    let Tom: Person = {
      name: 'Tom',
      age:28,
      website:'http://www.baidu.com',
    };
    // examples/playground/index.ts(9,3): error TS2322:
    // Type '{ name: string; age: number; website: string; }' is not assignable to type 'Person'.
    // Object literal may only specify known properties, and 'website' does not exist in type 'Person'.
    
    任意属性

    有的时候我们希望几口允许任意的属性,可以使用如下的方式:

    interface Person{
      name:string;
      age?:number;
      [propName:string]:any;
    }
    
    let kim :Person ={
      name:'kim',
      website:'http://www.baidu.com',
    };
    

    我们对于上面的代码进行解析,使用[propName:string]定义了任意属性取string类型的值。但是需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性:

    interface Person{
      name:string;
      age?:number;
      [propName:string]:string;
    }
    
    let kim :Person ={
      name:'kim',
      age:25,
      website:'http://www.baidu.com',
    }
    
    // index.ts(3,3): error TS2411: 
    //Property 'age' of type 'number' is not assignable to string index type 'string'.
    // index.ts(7,5): 
    //error TS2322: Type '{ [x: string]: string | number; name: string; age: number; website: string; }' is not 
    //assignable to type 'Person'.
    // Index signatures are incompatible.
    //Type 'string | number' is not assignable to type 'string'.
    //Type 'number' is not assignable to type 'string'.
    

    上例中,任意属性的值允许是 string,但是可选属性 age 的值却是numbernumber 不是string的子属性,所以报错了。

    另外,在报错信息中可以看出,此时 { name: ‘Xcat Liu’, age: 25, website: ‘http://xcatliu.com’ } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; website: string; },这是联合类型和接口的结合。

    只读属性

    有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用readonly定义只读属性:

    interface Person {
      readonly id: number;
      name: string;
      age?: number;
      [propName: string]: any;
    }
     
    let xcatliu: Person = {
      id: 89757,
      name: 'kim',
      website: 'http://www.baidu.com',
    };
     
    xcatliu.id = 9527;
     
    // index.ts(14,9): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
    

    上面的id属性是只读属性,id在初始化之后,又被赋值了,所以会报错。只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。

    interface Person {
      readonly id: number;
      name: string;
      age?: number;
      [propName: string]: any;
    }
     
    let xcatliu: Person = {  // 没有给id进行赋值
      name: 'Xcat Liu',
      website: 'http://xcatliu.com',
    };
     
    xcatliu.id = 89757; // 错误
     
    // index.ts(8,5): error TS2322: Type '{ name: string; website: string; }' is not assignable to type 'Person'.
    //   Property 'id' is missing in type '{ name: string; website: string; }'.
    // index.ts(13,9): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
    

    数组的类型

    [类型+方括号]表示法

    最简单的方法是使用[类型+方括号]来标识数组:

    let arr:number[] =[1,2,3,4,5]
    

    数组的项中不允许出现其他的类型:

    let arr:number[] =[1,'2',3,4,5];
    
    //index.ts(1,5): error TS2322: Type '(number | string)[]' is not assignable to type 'number[]'.
    //Type 'number | string' is not assignable to type 'number'.
    //Type 'string' is not assignable to type 'number'.
    

    现在的[1,'2',3,4,5]的类型被推断称为(number|string)[],这个是联合类型和数组的结合。

    let arr2: number[] = [1,2,3,4,5,6,7];
    arr2.push('8')
    
    //index.ts(2,16): error TS2345: Argument of type 'string' is 
    //not assignable to parameter of type 'number'.
    
    数组泛型

    也可以使用数组泛型(GenericArray<elemType> 来表示数组:

    let arr3: Array<number> = [1, 1, 2, 3, 5];
    
    用接口表示数组

    接口也可以用来描述数组:

    interface NumberArray{
      [index:number]:number;
    }
    let arr4 : NumberArray=[1,1,2,3,4];
    

    NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number

    any 在数组中的应用

    一个比较常见的做法是,用any表示数组中允许出现任意类型:

    let list : any[] =['kim',25,{website:'www.baidu.com'}];
    
    类数组

    类数组不是数组类型,比如arguments

    function sum(){
      let args:number[] = arguments;
    }
    // index.ts(2,7): error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
    // Property 'push' is missing in type 'IArguments'.
    

    事实上常见的类数组都有自己的接口定义,如 Arguments,NodeList, HTMLCollection 等:

    function sum() {
      let args: IArguments = arguments;
    }
    

    函数的类型

    函数的声明
    JavaScript中,有两种常见的定义函数的方式--函数声明(Function Declaration)和函数表达式(Function Expression):

    // 函数声明(Function Declaration)
    function sum (x,y){
      return x+y;
    }
    //函数表达式(Function Expression)
    let mySum = function (x,y){
      return x + y;
    }
    

    一个函数有输入和输出,要在TypeScript中对齐进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义比较简单:

    function sum (x:number,y:number):number{
      return x+y;
    }
    

    但是输出多余的(或少于要求)参数,是不被允许的:

    function sum (x:number,y:number):number{
      return x+y;
    }
    sum (1,2,3);
    // index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
    
    sum (1);
    //index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
    
    函数表达式

    如果我们现在想要写一个函数表达式的定义,我们可能会这样写:

    let muSum = function(x:number,y:number):number{
      return x+y;
    }
    

    这个是可以通过编译的,但是在事实上,上面的代码只对等号右侧的匿名行数进行了类的定义,而等号左侧的mySum,是通过赋值操作对类型进行推断出来的。如果需要我们手动给mySum添加类型,代码应该只这样写的:

    let mySum:(x number,y:number) =>number = function (x:number,y:number):number{
      return x+y;
    }
    

    TypeScript的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型,应用是十分广泛的。

    接口中函数的定义

    我们也可以使用接口的当时来定义一个函数需要符合的形象:

    interface SearchFunc{
      (source:string,subString:string):boolean
    }
    
    let mySearch : SearchFunc;
    mySearch  = function (source:string,subString:string){
      return source.search(subString) !== -1;
    }
    
    可选参数

    前面,输入多余的(或是少于要求的)的参数,是不被允许的,那么怎么定义可选的参数呢?与接口中的可选属性相似,也是使用表示可选参数:

    function buildName(firstName: string, lastName?: string) {
      if (lastName) {
        return firstName + ' ' + lastName;
      } else {
        return firstName;
      }
    }
    let kimXue= buildName('Xcat', 'Xue');
    let kim= buildName('kim');
    

    需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了:

    function buildName(firstName?: string, lastName: string) {
      if (firstName) {
        return firstName + ' ' + lastName;
      } else {
        return lastName;
      }
    }
    let kimXue= buildName('Xcat', 'Xue');
    let kim= buildName('kim');
     
    // index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
    

    上面的代码会报错,因为在first那么这个属性的后面还有必须的属性,因此报错。

    参数默认值

    ES6中,我们允许给函数的参数添加默认值,TypeScript会将添加了默认值的参数识别为可选参数:

    function buildName(firstName: string, lastName: string = 'Xue') {
      return firstName + ' ' + lastName;
    }
    let kimXue= buildName('Xcat', 'Xue');
    let kim= buildName('kim');
    

    此时就不受「可选参数必须接在必需参数后面」的限制了:

    function buildName(firstName: string = 'kim', lastName: string) {
      return firstName + ' ' + lastName;
    }
    let kimXue= buildName('Xcat', 'Xue');
    let kim= buildName('kim');
    
    剩余参数

    ES6中,可以使用...rest的方式获取函数中的剩余参数(rest参数):

    function push(array,...items){
      items.forEach(function (item){
        array.push(item);
      })
    }
    
    let a = [];
    push(a,1,2,3);
    

    事实上,items是一个数组,所以我们可以用数组的类型去定义它:

    function push(array:any[],...items:any[]){
      items.forEach(function (item){
        array.push(item);
      })
    }
    
    let a = [];
    push(a,1,2,3);
    

    和上面的代码一样,rest参数只能是最后一个参数。

    重载

    重载允许一个函数接受不同数量或是类型的参数,并作出不同的处理。
    比如,我们需要实现一个函数reverse,输入数字123的时候,输出的就是数字321,输入字符串‘hello’的时候,输出翻转的字符串‘olleh’,利用联合类型,我们可以这么实现:

    function reverse(x: number | string): number | string {
      if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
      } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
      }
    }
    

    然而这样写的话,还是有一个缺点,就是不能够精确地表达,输入为数字的时候,输出的也应该是数字,输入为字符串的时候,输出也应该为字符串。这时,我们可以用重载来实现多个这样的函数:

    function reverse(x: number): number;
    function reverse(x: string): string;
    function reverse(x: number | string): number | string {
      if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
      } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
      }
    }
    

    类型断言

    类型断言可以用来绕过编译器的类型判断,手动指定一个值的类型。

    语法
    <类型>值
    // 或
    值 as 类型
    // 在TSX语法 (React的JSX语法的TS版)中必须用后一种
    

    例子:将一个联合类型的变量指定为一个更加具体的类型

    TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

    function getLength(something: string | number): number {
      return something.length;
    }
     
    //index.ts(2,20): error TS2339: Property 'length' does not exist on type 'string | number'.
    //Property 'length' does not exist on type 'number'.
    

    这样的时候我们就需要使用类型断言,现将something断言成string

    function getLength(something: string | number): number {
      if ((<string>something).length) {
        return (<string>something).length;
      } else {
        return something.toString().length;
      }
    }
    

    类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:

    function toBoolean(something: string | number): boolean {
      return <boolean>something;
    }
     
    // index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
    //   Type 'number' is not comparable to type 'boolean'.
    

    声明文件

    当我们使用第三方库时,我们需要引用它的声明文件:

    声明语法

    当我们使用第三方库,比如jQuery,我们通常这样获取一个idfoo元素:

    $('#foo');
    // or
    jQuery('#foo');
    

    但是在 TypeScript 中,我们并不知道$jQuery是什么东西。这时,我们需要使用 declare关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型对不对。

    declear var jQuery:(string) =>any;
    jQuery('#foo');
    

    declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

    声明文件

    通常我们会把类型声明放在一个单独的文件中,这就是声明文件:

    // jQuery.d.ts
     
    declare var jQuery: (string) => any;
    

    我们约定声明文件以.d.ts为后缀。
    然后在使用到的文件的开头,用「三斜线指令」表示引用了声明文件:

    /// <reference path="./jQuery.d.ts" />
    jQuery('#foo');
    

    相关文章

      网友评论

          本文标题:TypeScript入门教程

          本文链接:https://www.haomeiwen.com/subject/olhwyqtx.html