TypeScript 語法學習(一)

TypeScriptJavaScript 的超集

安裝

npm i typescript -g

使用

tsc xx.ts文件路徑 輸出文件路徑

數據類型

TypeScript 包含 JavaScript 的類型,加了一些類型。只不過聲明的時候記得加 :類型名稱

基本數據類型

let a:number = 1;
let bool:boolean = true;
let b:string = 'hello world';
let c:string = `${b}`;
// 其實還有 null 和 undefined 類型,但是聲明它們也沒什麼用,就不使用它們了。

數組

let list:number[] = [1,2,3];		// 推薦
let a: Array<number>= [1,2,3];		// 使用泛型創建數組,和上一句效果一樣

元組

let x:[string,number] = ['12',1];	// 像這樣聲明賦值的時候,前一個必須是字符串,後一個必須是數字,而且只能是兩個元素,其他情況都會報錯。

x[3] = 2 這樣的添加元素會報錯;如果後面添加元素使用的是 push 的話,必須是 stringnumber 類型的,其他類型會報錯。

枚舉

enum myNumber{
    one = 1,
    two,
    three,
}
// 枚舉類型 JavaScript 沒有。和 java 的枚舉一樣,都是使用枚舉來將名字弄得更友好

one = 1 是什麼意思?就是從 one = 1 開始,依次 two = 2 … ,當然你也可以手動賦值也是可以的

我們可以看看編譯過來的源碼, .js:

var myNumber;
(function (myNumber) {
    myNumber[myNumber["one"] = 1] = "one";
    myNumber[myNumber["two"] = 2] = "two";
    myNumber[myNumber["three"] = 3] = "three";
})(myNumber || (myNumber = {}));

可以通過 myNumber.one 獲取 1,也可以通過 myNumber[1] 獲取 'one'

枚舉其實就是對象

Any

有的時候,我們在寫的時候,我們自己都不知道是什麼類型(主要是類型可能變動),這時候我們就可使用 any 類型,能直接跳過編譯階段的檢查。

let x:any = 4;
x = 'sdfsdf';
x = 1123;

還有忘說了,如果你直接這樣寫 let x = 1typescript 會直接推斷類型爲 number ,後面賦值其他類型就會報錯。

void

某種程度上來說,void類型像是與any類型相反,它表示沒有任何類型。 當一個函數沒有返回值時,你通常會見到其返回值類型是 void

function print(b) :void {
    console.log(b)
}; // void 表示空值,就是沒什麼東西,在這裏就是不返回值。

Never

never類型表示的是那些永不存在的值的類型。 例如, never類型是那些總是會拋出異常或根本就不會有返回值的函數表達式或箭頭函數表達式的返回值類型; 變量也可能是 never類型,當它們被永不爲真的類型保護所約束時。

function error(message: string) : never {
    throw new Error(message);
}
error('你好');

never類型是任何類型的子類型,也可以賦值給任何類型;然而,沒有類型是never的子類型或可以賦值給never類型(除了never本身之外)。 即使 any也不可以賦值給never

Object

學過 JavaScript 應該都知道這是什麼。

let obj: object = {x:1};

類型轉換

typescript 中就是類型斷言,告訴機器我知道我在幹什麼,有兩種方法:

// 第一種,使用 <>
let str: any = "this is a string";
let strLength: number = (<string>str).length

// 第二種,使用 as
let str: any = "this is a string";
let strLength: number = (str as string).length;

變量聲明

var 是 es5 的知識,不瞭解的可以看 《JavaScript高級程序設計》

letconst 是 es6 的知識,可以去看阮老師的 https://es6.ruanyifeng.com/ es6 教程,也可以去看我以前寫過的 es6 博客。

解構

還不熟悉的 解構 的建議去看 https://es6.ruanyifeng.com/#docs/destructuring

我寫一下元組、數組、對象的結構

// 元組
let input:[number,string] = [1,"sdf"];
let [first,second]:[number,string] = input;
// 數組
let [a, ...b] = [1,2,3,4];
// 對象
let x = {
    a: 1,
    b:"sdfsdf",
    c:3,
}
let {a,b}:{a:number,b:string} = x;

還有擴展

// 數組
let one = [1,2,3];
let two = [5,6];
let mynum = [0,...one,...two];
// 對象
let defaults = {
    one:1,
    two:2,
    three:3
}
let search = {...defaults,one: 11};

接口

這個 接口 es6 是沒有的,是 typescript 纔有的(Java早就有了),所以這個需要重點掌握。

什麼是接口?

簡單來說,就是人爲定義的一套標準,如果你想要通過我這個標準就必須遵守我這個標準,否則就是會報錯的。舉例:我們使用的網線的水晶頭(全世界通用,都採用RJ-45 這個標準),這裏的水晶頭就是我說的接口。

看一下官方的定義:

TypeScript的核心原則之一是對值所具有的結構進行類型檢查。 它有時被稱做**“鴨式辨型法”“結構性子類型化”**。 在TypeScript裏,接口的作用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約。

定義

// 定義
interface LabelledValue{
    label:string
}
// 定義
function printtable(labelledObj: LabelledValue) { // 使用
    console.log(labelledObj.label);
}

let myObj = {size:10,label: 'Size 10 Object'};

printtable(myObj);

傳過來的參數 myObj 中必須有 label 屬性,並且還必須是 string 類型,否則就會報錯。你也肯定注意到了,這裏多了一個 size 的屬性,也就是說,傳遞過來的對象可以包含更多的屬性,但前提還要包含接口中的屬性,否則也是會報錯的。

可選屬性

可選屬性就是在有的時候需要使用,有的時候不需要使用,這個時候我們就可使用屬性,讓函數傳入的對象只有部分屬性賦值

interface SquareConfig {
    color?: string;
    width?: number;
}
  
function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

以上例子就可以看到,在 colorwidth 屬性後面加一個 ? (英文問號)就表示該屬性是可選的屬性了

只讀屬性

聽名字就知道怎麼回事哈!就是傳入的值是只讀的,是不可以進行修改的。在對象中只需要在前面加一個 readonly ,使用泛型創建只讀數組的話可以使用 ReadonlyArry ,這個其實也是官方定義的一種藉口,底層還是使用的 readonly

interface Point {
    readonly x: number;
    readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

以上的例子是官方的

額外屬性檢查

簡單的理解就是通過一些手段繞過 對象字面量 的檢查。

給一個官方的例子:

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

注意我傳入的參數並是 colour ,並不是 color 這時候 typescript 的會報錯,那如何讓檢查通過呢?

第1種

修改

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

使用類型轉換可以

第2種 (推薦)

修改接口

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

添加了 [propName: string]: any 就會無視你傳入的什麼東西,都會通過。其實它是一種 索引,後面會提到

第3種

將你需要傳入的東西先賦值給一個變量,之後在傳入這個變量,這樣也可以跳過檢查

let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

其實個人感覺這個 額外屬性檢查 一般我們都不會使用到,感覺有點雞肋,最好還是不要使用,如果你有一些特殊的用途的話可以考慮一下。

函數類型

函數類型的接口感覺有點奇怪。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

這樣我就定,義了一個函數類型的接口,也可以叫 調用簽名 ,括號裏面的就是函數的 參數,冒號後面的就是 返回參數的類型

使用它的時候就像下面那樣使用它:

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) : boolean {
  let result = source.search(subString);
  return result > -1;
}

其實函數的參數名不需要與接口裏定義的名字相匹配,甚至我們可以省略一些參數,畢竟 typescript 可以推斷的。

let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

索引的類型

簡單理解就是 可以通過索引來獲取值,可以分爲 字符串類型的索引數字類型的索引

interface StringArray {
    [index: number]: string;
}
  interface obj {
    [index: string]: string;
}

還可以將返回類型弄成自己定義的類名:

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 錯誤:使用數值型的字符串索引,有時會得到完全不同的Animal!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

這裏會報錯的,typescript 規定:同時使用兩種類型的索引時,數字索引的返回值必須是字符串索引返回值類型的子類型 ,你只需要把 AnimalDog 互換一下就行了。這種設計是爲了在使用 數字 進行訪問值的時候可以轉換爲 string 來進行訪問,例如 x[0] 轉換爲 x[‘0’]

interface NumberDictionary {
  [index: string]: number;
  length: number;    // 可以,length是number類型
  name: string       // 錯誤,`name`的類型與索引類型返回值的類型不匹配
}
// 上面的代碼修改後,可以創建對象
let x : NumberDictionary;
x={length:0,name:1};

對了,還可以進行只讀設置

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
// 這兩種創建方法是一個意思,上面等於下面,下面也等於上面
let myArray: ReadonlyStringArray = {
    0:'Alice',   1: 'Bob',
}
myArray[2] = "Mallory"; // error!

類類型

像 java 一樣我們可以使用接口來定義類,通過定義接口來對類的屬性和方法進行約束。

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

implements 實現繼承時,類要自己實現所有的接口,接口中的類是公共部分,類中可以定義一些私有部分

類靜態部分與實例部分的區別

類是具有:靜態類型和實例類型兩種的,官網實例

interface ClockConstructor {
    new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

它會報錯,爲什麼?其中 constructor存在於類的靜態部分,而類實現的接口是一個實例類型,兩者之間是會出問題的。如果想要使用,可以這樣

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

繼承接口

類可以繼承接口,當然,接口也是可以進行繼承的。

單接口的繼承

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

多接口的繼承

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合類型

可以讓一個對象同時具有函數和對象的特性。

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口繼承類

接口也是可以繼承類的。但是使用的場景並不多。

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 錯誤:“Image”類型缺少“state”屬性。
class Image implements SelectableControl {
    select() { }
}

class Location {

}

並不常用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章