TypeScript入門指南(基礎篇)

慕晨同學發表於2019-07-27

文章首發於:github.com/USTB-musion…

寫在前面

ts是擁有型別系統的js的超集,近年來非常火熱。可以這麼說,ts才是真正意義上的js。雖然ts的官方文件非常全面,但是對於原來沒有接觸過ts的同學來說,全篇通讀下來需要耗掉不少時間,這篇文章旨在為嘗試入門ts的同學使用。

本文將從以下幾部分進行總結:

  1. TypeScript的優勢
  2. 強型別與弱型別的區別
  3. 動態型別與靜態型別的區別
  4. 基礎型別
  5. 介面型別
  6. 函式
  7. 泛型

TypeScript的優勢

1.幫助更好地重構程式碼

一個好的程式碼習慣是常常對自己寫的程式碼進行小的重構,使得程式碼可維護性更強。但是對於很多線上執行的程式碼,程式碼測試覆蓋率往往不是很高,有時候哪怕一個變數名的改動,都會牽一髮而動全身。而對於使用ts編寫的專案就不會有這種擔心。ts的靜態檢查特性會幫助找出程式碼中有錯誤的部分。

2.vscode等IDE的提示更加智慧

js是一門動態弱型別解釋語言,變數宣告後可以改變型別,而且型別需要在執行時才能確定。而ts的報錯提示是在編譯時,不是在執行時。所以使用ts帶來的靜態型別檢查等特性將使得IDE的提示更加完善。

3.型別宣告本身就是非常好的文件

當你接手一個有歷史包袱的專案時,肯定會頭疼於文件和程式碼註釋的缺失,而對於ts來說,是可以做到程式碼即文件的,通過宣告檔案可以知道哪些欄位的含義以及哪些欄位是必填和選填的。舉個簡單例子,當封裝一個button的元件時:


export interface ButtonProps {
  style?: React.CSSProperties
  className?: string
  label?: React.ReactNode
  type?: 'primary' | 'default' | 'search'
  size?: 'sm' | 'md' | 'lg' | 'mini'
  disabled?: boolean
  title?: string
  onClick?: ((e: React.MouseEvent<HTMLButtonElement>) => void)
}
複製程式碼

通過這些宣告檔案可以知道,當使用這個button檔案時,style是一個可選值,表示一個可以自定義樣式的style欄位。type也是一個可選值,表示按鈕的顏色型別,可以選擇'primary','default','mini'其中的一種。disabled也是一個可選值,傳入的值必須是boolean型別。所以就可以看出型別宣告本身就是非常好的文件。

強型別與弱型別的區別

強型別語言: 強型別語言不允許改變變數的資料型別,除非進行強制型別轉換。

例如:如果定義了一個字串變數str,如果沒有進行強制型別轉換,是把str不能當作布林值,整型等非字元型進行處理的。c,c++,Java等都是強型別語言。

弱型別語言: 定義與強型別語言相反,一個變數可以被賦予不同資料型別的值。

var a = '111';
var b = 222;
a = b;
console.log(a) // 222
複製程式碼

如以上的js程式碼所示,a是一個字串變數,b是一個整型變數,但是卻可以把b賦值給a,把a列印出來的值是222。

強型別的嚴謹效能有效地避免很多錯誤。

動態型別與靜態型別的區別

動態型別語言: 在執行階段才做型別檢查。

例如:js/python等就是屬於動態型別語言,對型別檢查非常寬鬆,bug可能隱藏很久才被發現。

靜態型別語言: 在編譯階段就做型別檢查

例如: c++/Java等屬於靜態型別語言,對型別檢查非常嚴格,bug在編譯階段就會被發現。能做到程式碼即文件。

基礎型別

ES6的型別可以分為Boolean,Number,String,Array,Function,Object,Symbol,undefined,null。而TypeScript的資料型別則在ES6的基礎上加上void,any,never,元組,列舉,高階型別。

基本語法

: type

TypeScript的基本型別語法是在變數之後使用冒號進行型別標識,這種語法也揭示了TypeScript的型別宣告實際上是可選的。

boolean

boolean是最基礎的資料型別,在ts中,使用boolean來定義布林值

let isDone: boolean = false;
複製程式碼

number

在ts中,使用number來定義數值型別

let num: number = 123

複製程式碼

string

在ts中,使用string來定義字串型別

let name: string = 'jarod'
複製程式碼

array

在ts中,定義陣列方式有兩種: 一種是在可以在元素型別後面接上[],表示由此元素組成的一個陣列:

let arr1: number[] = [1, 2, 3]
複製程式碼

還有一種是使用陣列泛型,Array<元素型別> :

let arr2: Array<number> = [1, 2, 3]

複製程式碼

元組

如果想在陣列內表示不同元素怎麼辦?這時候就需要使用元組型別了。元組型別允許表示一個已知元素數量和型別的陣列,各元素的型別不必相同。 比如,你可以定義一對值分別為number和string型別的元組。

let hello: [number, string] = [0, 'hello']
複製程式碼

列舉

enum型別是對JavaScript標準資料型別的一個補充。 像C#等其它語言一樣,使用列舉型別可以為一組數值賦予友好的名字。

enum Month {
    Jan,
    Feb,
    Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
複製程式碼

never

如果一個函式永遠沒有返回值時,我們可以宣告其為void型別:

function example(): never {
    throw new Error('never');
}
複製程式碼

any

any是ts的一個特殊型別,一旦宣告為any,則意味著關閉來ts的型別檢查,

let x: any = 0;
x = [];
x = false; 
x = '';
複製程式碼

對於any型別的變數,可以賦予任何型別的值。使用any對遷移js的專案是很友好的。但是在真正開發中,儘量還是少用any為好。

void

在ts中,void表示函式沒有返回值。

function example(): void {
    console.log('this is void type');
}
複製程式碼

undefined和null

在TypeScript裡,undefined和null兩者各自有自己的型別分別叫做undefined和null。 和 void相似,它們的本身的型別用處不是很大:

let u: undefined = undefined;
let n: null = null;
複製程式碼

readonly

一些物件屬性只能在物件剛剛建立的時候修改其值。 你可以在屬性名前用 readonly來指定只讀屬性,在結合react使用的過程中的例子:

interface Props {
    readonly name: string;
}
interface State {
    readonly color: string;
}
export class Child extends React.Component<Props,State> {
  childMethod() {
    this.props.name = 'jarod'; // ERROR: (props are immutable)
    this.state.color = 'red'; // ERROR: (one should use this.setState)  
  }
}
複製程式碼

介面型別

在 TypeScript 中,我們使用介面(Interfaces)來定義物件的型別。

物件介面

賦值的時候,變數的形狀必須和介面的形狀保持一致。

interface Name {
    first: string;
    second: string;
}

var personName:Name = {
    first: '張三'
} // Property 'second' is missing in type '{ first: string; }' but required in type 'Name'
複製程式碼

ts會對每一個欄位做檢查,如果沒有對介面中宣告的欄位進行定義(非可選),可以看出,定義的變數比介面少一些屬性則會丟擲錯誤。

函式介面

介面能夠描述JavaScript中物件擁有的各種各樣的外形。除了描述帶有屬性的普通物件外,介面也可以描述函式型別

interface Lib {
    (): void;
    version: string;
    doSomething(): void;
}

function getLib() {
    let lib = (() => {}) as Lib
    lib.version = '1.0.0'
    lib.doSomething = () => {}
    return lib;
}
複製程式碼

函式

函式宣告

function sum(x: number, y: number) {
    return x + y
}
複製程式碼

函式表示式

let sum =  (x: number, y: number): number => x + y
複製程式碼

可選引數

對於引數,我們可以宣告其為可選引數,即在引數後面加"?"

function buildName(firstName: string, lastName?: string) {
    // ...
}
複製程式碼

函式過載

過載允許一個函式接受不同數量或型別的引數時,作出不同的處理。

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('');
    }
}
複製程式碼

抽象類

抽象類只能在例項中使用,不能直接被例項化。

abstract class Animal {
    eat() {
        console.log('eat')
    }
    abstract sleep(): void
}

class Dog extends Animal {
    constructor() {
        super()
    }
    sleep() {
        console.log('Dog sleep')
    } // 在子類中實現父類中的抽象方法
}
複製程式碼

public, private和protected

TypeScript 可以使用三種訪問修飾符(Access Modifiers),分別是 public、private 和 protected。

public

public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,預設所有的屬性和方法都是 public 的

class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
複製程式碼

private

private 修飾的屬性或方法是私有的,不能在宣告它的類的外部訪問

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';

// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
複製程式碼

protected

protected 修飾的屬性或方法是受保護的,它和 private 類似,區別是它在子類中也是允許被訪問的

class Animal {
    protected name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}
複製程式碼

泛型

定義:不預先確定的資料型別,具體的型別需要在使用的時候才能確定

例子: 宣告一個列印函式,實現把傳入的字串列印出來:

function log(value: string): string {
    console.log(value)
    return value
}
複製程式碼

但是這時,加一個需求,要實現能把字串陣列也列印出來:

function log(value: string): string
function log(value: string[]): string[]
function log(value: any): {
    console.log(value)
    return value
}
複製程式碼

如上所示,可以用之前的函式過載來實現。

如果這時,再加一個需求,要實現能把任何型別的引數列印出來。泛型就派上用場了:

function log<T>(value: T): T {
    console.log(value);
    return value;
}
複製程式碼

軟體工程中,我們不僅要建立一致的定義良好的API,同時也要考慮可重用性。元件不僅能夠支援當前的資料型別,同時也能支援未來的資料型別,這在建立大型系統時為你提供了十分靈活的功能。

後續

Typescript實踐中的進階篇即將到來~

相關文章