TypeScript語法基礎


什么是TypeScript?

TypeScript是微軟開發的一門編程語言,它是JavaScript的超集,即它基於JavaScript,拓展了JavaScript的語法,遵循ECMAScript規范(ES6/7/8+)。

TypeScript = Type + Script(標准JS),它可以編譯成純JavaScript,已經存在的JavaScript也可以不加改動地在TS的環境上運行。

目前, Angular 已經使用 TypeScript 重構了代碼,另一大前端框架 Vue 的3.0版本也將使用 TypeScript 進行重構。在可預見的未來,TypeScript 將成為前端開發者必須掌握的開發語言之一。

為什么要使用TypeScript?

  1. 提升開發效率。
  2. 提升可維護性。
  3. 提升線上運行質量。TS有編譯期的靜態檢查,加上IDE的智能糾錯,盡可能的將BUG消滅在編譯器上,線上運行時質量更穩定可控。
  4. 可讀性強,適合團隊協作

TypeScript開發環境

npm install -g typescript  // 安裝ts編譯器
tsc hello.ts // 手動編譯ts文件,會生成同名js文件
tsc --init  // 生成tsconfig.js文件

當然,我們可以配置webpack,開啟node服務,進行熱更新開發。

TypeScrip數據類型

學習數據類型前,要先明白兩個概念:

強類型和弱類型

強類型指一個變量一旦聲明,就確定了它的類型,此后不能改變它的類型。弱類型可以隨便轉換。TypeScript是強類型語言,JavaScript是弱類型語言。

靜態類型和動態類型

靜態類型語言:編譯階段檢查所有數據的類型。動態類型語言:將檢查數據類型的工作放在程序的執行階段,也就是說,只有在程序執行時才能確定數據類型。

基本類型

在ES6的基礎上,新增了void、any、never、元組、枚舉、高級類型。

布爾、數字、字符串

let bool: boolean = true;
let num: number = 10;
let str: string = "abc";
let abc: number | boolean | null; // 可以為一個變量聲明多種類型,除非有特殊需求,一般不建議這樣做。

數組

TypeScript的數組,所有元素只能是同一種數據類型。

let arr1: number[] = [1,2,3];
let arr2: Array<number> = [4,5,6]; // 數組的第二種聲明方式,與前面等價
let arr3: string[] = ["hello","array","object"];

元組

元組是特殊的數組,限制了元素的個數和類型。

let tuple: [number,string,boolean] = [10,"hello",true]; 
// tuple.push(5); // 元組越界,但不會報錯。原則上不能對tuple push,這應該是一個缺陷
// console.log(tuple[3]); // 新增的元素無法訪問。

函數

  • 函數的聲明定義(三種方式)
  • 函數傳參
  1.  可選參數必須放在必選參數的后面
  2.  使用ES6的默認參數,不需要聲明類型
  3.  使用ES6的剩余參數,需要聲明類型
// 方式一 箭頭函數:聲明和定義分開
let compute: (x:number, y:number) => number; // 函數聲明,規定了傳入參數、返回值的數據類型
compute = (a, b) => a+b;  // 函數定義時,參數名稱不必與聲明時的相同
// 方式二:箭頭函數:聲明的同時定義
let add = (x:number, y:number) => { return x+y }; // 返回值的類型可省略,這利用了ts的類型推斷功能
// 方式三:function關鍵字:聲明和定義分開
function add (x: number,y: number): number;
// 方式四:function關鍵字:聲明的同時定義
function add (x: number,y: number): number{ retrun x+y; }
//函數傳參:
function add789(x: number, y?: number, z=1, ...rest: number[]) {
    console.log(rest);
    return y ? x+y : x
}

對象

// 正確的寫法
let obj1: {x:number, y:number} = {x: 1, y: 2};
obj1.x = 3;

// 不建議的寫法
let obj: object = {x: 1, y: 2}; 
obj.x = 3; // 報錯,因為定義的時候繞過了聲明檢查,此時不知道是什么類型。

symbol

let s1: symbol = Symbol();
let s2 = Symbol();

undefind、null

let un: undefined = undefined;
let nu: null = null;
nu = null; // 這樣是允許的,需要將tsconfig中“strictNullChecks”置為false
un = undefined;

void

是一種操作符,可以讓任意一個表達式返回undefined。之所以引進void,是因為undefined不是一個保留字,可以在局部作用域內將其覆蓋掉。

let noReturn = () => {};

any

any 表示變量可以為任何類型,在TS中一般不用它。如果使用它,也便失去了使用TS的意義,與前面不建議為變量聲明多種類型是一個道理。

let x: any;
x = 1;
x = "str";

never

表示永遠不會有返回值的類型

let error = () => {
    throw new Error("errors")
};
let endless = () => {
    while(true)
    {}
};
// 以上兩個例子永遠不會有返回值

枚舉類型 enum

枚舉主要來定義一些常量,方便記憶,減少硬編碼,增加可讀性。

基本使用:

// 數字枚舉
enum Role {
    Reporter, // 默認從0開始
    Developer=5, // 也可指定某個值
    Maintainer,
}
console.log(Role); // 可以看到數據結構,能夠進行反向映射,即通過值來訪問成員
console.log(Role.Developer);  // 訪問枚舉成員

// 字符串枚舉 不可以進行反向映射
enum message {
    success = "成功了",
    fail = "失敗了"
}
console.log(message);

// 異構枚舉,將數字和字符串混用
enum Answer {
    N = 0,
    Y = "yes"
}

    注意:不能修改枚舉成員的值

枚舉成員的分類:

枚舉成員的分類:
    (1)常量枚舉成員
    (2)對已有枚舉成員的引用
    (3)常量表達式
    (4)非常量表達式。這種成員的值不會在編譯階段確定,在執行階段才會有
例:
enum Char {
    a,
    b = 9,
    c = message.success,
    d = b,
    e = 1 + 3,
    f = Math.random(),
    g = "123".length,
}
console.log(Char);

常量枚舉和枚舉類型

// 常量枚舉 用const聲明的枚舉都是常量枚舉。特性:在編譯階段被移除,編譯后不會出現
// 作用:當我們不需要對象,只需要對象的值的時候
const enum Month{
    Jan,
    Feb,
    Mar,
}
let month = [Month.Jan,Month.Feb,Month.Mar];
console.log(month);

// 枚舉類型 枚舉可以作為一種類型
let e: Role = 2;
let e1: Role.Developer = 12;  // 數字枚舉類型與number類型相互兼容,因此可以復制
console.log(e,e1); // 按照Role的類型去聲明新變量

let g1: message.success = "hello"; // 報錯,字符串枚舉類型message.success與string類型不兼容

e === e1; // 可比較
e === g1; // 不可比較,因為類型不一樣

interface接口

接口可以用來約束對象、函數、類的結構和類型,是一種契約,並且聲明之后不可改變。

1.定義 (interface關鍵字)

interface List {
    id: number;
    name: string;
}

interface Result {
    data: List[]; // 表示由List接口組成的數組
}

function render(result:Result) {
    result.data.forEach((value)=>{
        console.log(value);
    })
}

let result = {
    data:[
        {id:1,name:"a"}, 
        {id:2,name:"b"},
    ],
};
render(result);

2.內部規范了什么?

通過上述例子,看到接口規范了成員名稱、成員的的類型、值的類型。

此外,還可以規范成員屬性。

3.成員屬性

可選屬性和只讀屬性

interface List {
    readonly id: number; // readonly表示只讀屬性
    name: string;
    age?: number; // ?表示可選屬性
}

4.索引簽名

當不確定接口中有多少屬性的時候,可以用索引簽名。

格式:[x: attrType]: valueType  分別規定了成員的類型和值的類型,即通過什么來索引和訪問的值的類型。

一般通過數字和字符串來索引,也可以兩者混合索引。

// 用數字索引
interface StringArray {
    [index: number]: string; // 表示,用任意的數字去索引StringArray,都會得到一個string。這就相當於聲明了一個字符串類型的數組
}
let chars: StringArray = ["A","B"]; // 此時,chars就是一個字符串數組,我們可以用下標去訪問每個元素
console.log(chars,chars[0]);

// 用字符串和數字混合索引
interface Names {
    [x: string]: string; // 用任意的字符串去索引Names,得到的結果都是string。
    // y: number; // 此時不能聲明number類型的成員
    // [y: number]: number // 報錯,因為x和y的值string和number類型不兼容
    [z: number]: any; // 兩個簽名的返回值類型之間要相互兼容。為了能保持類型的兼容性。
}
let names: Names = {"ming":"abc",1:"45"};
console.log(names[1],names["ming"]); // 通過數字索引、通過字符串索引

※ 注意值的類型要兼容

  (1)索引簽名和普通成員

    如果設置了[x: string]: string,不能再設置y: number。如果設置了[x: string]: number不能再設置y: string

  (2)索引簽名和索引簽名

    如果多個索引簽名的值不同,要注意相互兼容,比方any和string

5.函數傳參時如何繞過類型檢查

如果在接收的后端數據中,比約定好的接口多了一個字段,能否通過類型檢查?會不會報錯?

let result = {
    data:[
        {id:1,name:"a",sex:"man"}, 
        {id:2,name:"b"},
    ],
};
render(result); // 這樣是不會報錯的,只要滿足接口約定的必要條件即可

render({
    data:[
        {id:1,name:"a",sex:"man"},
        {id:2,name:"b"},
    ],
}); // 但如果這樣調用,會報錯,因為無法通過sex:"man"的類型檢查。這時候需要用其他方法

我們有三種方法:

  1. 通過接口定義變量,函數調用時傳入變量名(只對必要的約定條件進行檢查,多余的數據不做檢查)
  2. 類型斷言(所有約定都不做類型檢查,失去了ts類型檢查的意義)
  3. 索引簽名

 第一種方法已經在上面做了示例,我們看后面兩種方法如何做:

// 類型斷言
render({
    data:[
        {id:"b",name:3,sex:"man"},
        {id:2,name:"b"},
    ],
}as Result); // 明確告訴編譯器,數據符合Result,這樣,編譯器會繞過類型檢查
render(<Result>{ 
    data:[
        {id:1,name:"a",sex:"man"},
        {id:2,name:"b"},
    ],
}); // 與上等價,但在React中容易引起歧義。不建議使用

// 索引簽名
interface List {
    id: number;
    name: string;
    [x: string]: any; // 字符串索引簽名。用任意字符串去索引List,可以得到任意的結果,這樣List接口可以支持多個未知屬性
}

在什么場景下用什么方法,需要我們熟知這三種方法的特性

6.接口和函數

接口可以用來定義函數的傳參、返回值的類型

interface Add1 {
    (x: number,y: number): number;
}
let add1: Add1 = (a,b) => a+b;

此外,還可以用類型別名來定義函數

type Add2 = (x: number,y: number) => number;
let add2: Add2 = (a,b) => a+b; // 聲明+定義

我們再來總結一下函數的聲明定義方式:

  1. 普通聲明定義(function、箭頭函數)
  2. 接口定義類型
  3. 類型別名

另外,接口內也可以定義函數

// 混合類型接口
interface Lib {
    abc(): void;
    version: string;
    doSomething(): void;
}

function getLib(){
    let lib: Lib = {
        abc: ()=>{},
        version: "1.0",
        doSomething: ()=>{}
    };
    // let lib: Lib = {} as Lib; // 定義的時候,這種方式更方便
    lib.version = "1.0";
    lib.doSomething = () => {};
    return lib;
}
let lib1 = getLib();
console.log(lib1,lib1.version,lib1.doSomething());

class類

關於類的成員:

1.屬性必須有類型注解

2.屬性必須有初始值

3.屬性修飾符:

  (1)公有 public

    所有成員默認都是public。可以通過各種方式訪問。

  (2)私有 private

    私有成員只能在類中被訪問,不能被實例和子類訪問。如果給構造函數加上私有屬性,表示這個類既不能被實例化也不能被繼承。

  (3)受保護 protected

    受保護成員只能在類和子類中訪問,不能通過它們的實例訪問。如果給構造函數加上受保護屬性,表示這個類不能被實例化只能被繼承。也就是聲明了一個基類。

  (4)靜態 static

    靜態成員只能通過類名和子類名訪問,不能被實例訪問。

  (5)只讀 readonly

    只讀成員不能被修改。

4.以上屬性除了可以修飾成員,也可以修飾構造函數中的參數(static除外)。這樣可以省去構造函數之中外的類型注解,簡化代碼。

class Dog{
    constructor(name: string){
        this.name = name; // 屬性必須賦初值
    }
    name: string; // 必須要為屬性添加類型注解。
    run(){
        console.log("running");
        this.pri(); // 只能在類內部訪問私有成員
        this.pro();
    }
    private  pri(){ // 私有成員只能被類本身調用,不能被類的實例和子類調用
        console.log("pri是dog類的私有屬性");
    }
    protected pro(){ // 受保護成員只能在類和子類中訪問
        console.log("pro是dog類的受保護屬性");
    }
    readonly logs: string = "new";
    static food: string = "food"; // 靜態修飾后,只能通過類名調用,不能被子類和實例調用。靜態成員可以被繼承
}
let dog = new Dog("dog1");
dog.run(); 
console.log(Dog.food); // 通過類名訪問靜態成員

抽象類和多態

所謂抽象類(abstract),是只能被繼承,不能被實例化的類。

 抽象方法

在抽象類中,不必定義方法的具體實現,這就構成了抽象方法。在抽象類中使用abstratct關鍵字修飾后,不能定義該方法的具體實現。

抽象方法的好處是:實現多態。

interface AnimalParam{
    bigClass: string,
    environment: string,
    [x: string]: string;
}
abstract class Animal{  // 抽象類用abstract關鍵字修飾
    constructor(params: AnimalParam) {  // 構造函數參數使用接口是為了其子類在定義的時候方便傳參
        this.bigClass = params.bigClass;
        this.environment = params.environment;
    }
    bigClass: string;
    environment: string;
    abstract sleep(): void;
}

class Dogs extends Animal{
    constructor(props: AnimalParam){
        super(props);
        this.name = props.name;
    }
    name: string;
    run(){
        console.log("running");
    }
    sleep() {
        console.log("dog sleep")
    }
}
class Cat extends Animal{
    sleep(): void {
        console.log("cat sleep")
    }
}
let dog1 = new Dogs({bigClass:"a",environment:"b",name:"xiaoqi"});
let cat = new Cat({bigClass:"a",environment:"b"});

let animals: Animal[] = [dog1,cat];
animals.forEach(i=>{
    i.sleep();
});
 this

我們可以在方法中返回this,可以進行鏈式調用,非常方便。

class WorkFlow{
    step1(){
        return this;
    }
    step2(){
        return this;
    }
}
class Myflow extends WorkFlow{
    next(){
        return this;
    }
}
console.log(new WorkFlow().step1().step2());
console.log(new Myflow().next().step1().step2());

類和接口

類類型接口

  • 接口約束類成員有哪些屬性,以及它們的類型
  • 類在實現接口約束的時候,必須實現接口中描述的所有內容。可以多,不可以少
  • 接口只能約束類的公有成員,即不可以將接口中規定的成員置為非公有的屬性
  • 接口不能約束類的構造函數
interface Human {
    name: string;
    eat(): void;
}
class Asian implements Human{
    constructor(name: string){
        this.name = name;
    }
    name: string;
    eat(){}
    sleep(){}
}

接口繼承接口

interface  Man extends Human{
    run(): void
}
interface Child {
    cry(): void
}
interface Boy extends Man,Child{} // 多繼承,將多個接口合並成一個接口

接口繼承類

可以理解為,將類轉化成接口。接口集成類的時候,不僅抽離了公有成員,也抽離了私有成員、受保護成員。

如何理解呢?這么做的目的是限定接口的使用范圍,並不會真正為這個接口添加類的私有和受保護屬性,而這個限定范圍就是:只能由子類來實現這個接口。

class Auto{
    state = 1;
    private state2 = 0;
    protected state3 = 3;
}
interface AutoInterface extends Auto{} // 接口繼承類

class C implements AutoInterface{ // C在實現這個接口的時候,無法實現接口中的私有成員和受保護成員,因此報錯
    state = 1
}
class Bus extends Auto implements AutoInterface{ // Auto的子類Bus,遵循AutoInterface接口
    showMsg(){
        // console.log(this.state2);
        console.log(this.state3);
    }
}

let bus = new Bus();
bus.showMsg();
console.log(bus);

非空斷言操作符

它會排除掉變量中的 null 和 undefined,只需要從變量后面添加 ! 即可。

function simpleExample(a: number | undefined) {
   const b: number = a; // COMPILATION ERROR: undefined is not assignable to number.
   const c: number = a!; // OK
}

非空斷言操作符會經常在程序中用到,例如獲取 ref,它會被規范為 HTMLElement | null,默認值為 null,在需要使用的時候經常會被編譯器報錯,提示你這個變量有可能是 null。往往我們比編譯器更懂程序,在這個時候需要我們告訴編譯器,這個地方一定有值。

// 不使用 !,顯得有些啰嗦
const goToInput = () => ref.current && ref.current.scrollIntoView();
// 使用 !,代碼更簡潔
const goToInput = () => ref.current!.scrollIntoView();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM