一篇文章學會 TypeScript

一篇文章學會 TypeScript (內部分享標題:TypeScript 基礎)

這篇文章是我在公司前端小組內部的演講分享稿,目的是教會你們使用 TypeScript,
這篇文章雖然標着基礎,但我指的基礎是學完後就可以勝任 TypeScript 的開發工做。
從我分享完的效果來看,你們都學會了,效果仍是好的。我把這篇分享稿發佈出來,
但願能對你們的 TypeScript 學習有所幫助。

一、什麼是 TypeScript?

TypeScript 是由微軟發佈的一款開源的編程語言。它是 JavaScript 的超集(兼容 JavaScript 代碼),其代碼必須通過編譯後,方可在 JavaScript 環境中運行,核心功能是類型系統能夠提早使用 ES 的新特性javascript

接下來來看一段代碼示例:html

// TypeScript 語法
function add(x: number, y: number): number {
    const res: number = x + y;
    return res;
}

// 與 C 語言比較
int add(int x, int y) {
    int res = x + y;
    return res;
}

當類型不對的時候,IDE 會提示錯誤:
clipboard.png前端

編譯後:vue

// JavaScript 語法
function add(x, y) {
    const res = x + y;
    return res;
}

聯想:大體能夠把它當作是加了類型系統的 Babel。java

問題:爲何要學習 TypeScript?

  1. 類型系統能夠在代碼編譯階段就能夠發現潛在的變量類型問題,甚至在開發階段,IDE 就能夠幫助咱們找到這些潛在的問題,避免了項目在上線後才發現變量類型不對的錯誤。
  2. TypeScript 是前端開發的趨勢,有些特性還可能會成爲 ES 的新標準,Vue 3.0 也會採用 TypeScript 做爲開發語言,當你想看源碼的時候不至於看不懂,跟着趨勢走確定是沒錯的。
  3. 減小類型校驗等無用的代碼書寫。
  4. 能夠提早享受 ES 新特性帶來的便利。
  5. 向 qian 看,qian 途。

二、快速上手,三步走

第一步:全局安裝 TypeScript

$ yarn global add typescript

// 測試是否安裝成功
$ tsc -v
Version 3.4.5

第二步:建立 .ts 文件

// add.ts
function add(x: number, y: number): number {
    const res: number = x + y;
    return res;
}
export default add;

第三步:編譯

$ tsc add.ts

clipboard.png

// add.js
function add(x, y) {
    const res = x + y;
    return res;
}
export default add;

文件監聽,實時編譯

適用於邊開發邊看結果的狀況:react

$ tsc -w add.ts
Starting compilation in watch mode...

Watching for file changes.

三、TypeScript 的基本類型

// 布爾值
let isDone: boolean = false;
// 數值
let age: number = 12;
// 字符串
let name: string = 'Jay';
// 數組
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
// 對象
let mokey: object = {name: 'wu kong'};
// 空值,表示沒有返回值
function print(): void {}
// 任意值
let goods: any = {};
let goods: any = 2019;
// 未指定類型,視爲任意值
let goods;
// 類型推斷
let name = 'Jay';
name = 123; // 報錯
// 聯合類型
let name: string | number;
name = 'Jay';
name = 123;

四、類和接口

類是對屬性和方法的封裝webpack

類的示例:git

// 跟 JavaScript 的類類似,多了訪問控制等關鍵詞
class Monkey {
    public height: number;
    private age: number = 12;
    public static mkName: string = 'kinkong';
    private static action: string = 'jump';
    
    constructor(height: number) {
        this.height = height;
    }
    public getAge(): number {
        return this.age;
    }
}

const monkey = new Monkey(120);
monkey.getAge();

const height = monkey.height;
const age = monkey.age; // 報錯
const mkName = Monkey.mkName;
const action = Monkey.action; // 報錯
類的繼承
class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.bark();
dog.move(10);
protected、private、readonly 和 super
  • protected 和 private 都不能從外部訪問
  • protected 能夠被繼承,private 不能夠
class Person {
    protected name: string;
    private age: number;
    constructor(name: string, age: number) { 
        this.name = name; 
        this.age = age;
    }
}

class Employee extends Person {
    private readonly initial: string = 'abc';
    private readonly department: string;
    constructor(name: string, department: string) {
        super(name, 1);
        this.department = department;
    }
    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
    public getAge() {
        return this.age;
    }
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error
let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}
類能夠用於接口繼承
class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};
接口

接口是用來定義規範的,經常用做類型說明。
接口的示例:github

// 定義規範,限定類型
interface Point {
    x: number;
    y: number;
    // 可選屬性
    z?: number;
}
let pt: Ponit = {x: 1, y: 1, z: 1};

function setPoint(point: Point): void {}
setPoint({x: 1, y: 1});

// 實現接口
class point implements Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    public getPoint(): object {
        return {
            x: this.x,
            y: this.y,
        }
    }
}

const p = new point(3, 4);
p.getPoint();
接口繼承接口
interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}
初始化參數規範
interface ClockConstructor {
    new (hour: number, minute: number);
}

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

五、type、 namespace 和泛型

type 定義新類型

當 TypeScript 提供的類型不夠用時,能夠用來自定義類型,供本身使用。web

// type 定義新類型,至關於類型別名
type myType = string | number | boolean
// 使用:
const foo: myType = 'foo'

// type 定義函數類型
type hello = (msg: string) => void

// type 對象類型
type WebSite = {
    url: string;
    title: number;
}

namespace 命名空間

命名空間主要有兩個方面的用途:

  1. 組織代碼。組織一些具備內在聯繫的特性和對象,可以使代碼更加清晰。
  2. 避免名稱衝突。
namespace Validator {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

// 當聲明一個命名空間的時候,全部實體部分默認是私有的,可使用 export 關鍵字導出公共部分。
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
new Validator.LettersOnlyValidator();

interface MyClassMethodOptions {
    name?: string;
    age?: number;
}

namespace MyClass {
  export interface MyClassMethodOptions {
    width?: number;
    height?: number;
  }
}
使用外部定義的 namespace
/// <reference path="validate.ts" />
const v = new Validator.LettersOnlyValidator();

泛型

泛型容許在強類型程序設計語言中編寫代碼時,使用一些之後才指定的類型,在實際調用時纔去指定泛型的類型。

舉例:

// 模擬服務,提供不一樣的數據。這裏模擬了一個字符串和一個數值
const service = {
    getStringValue: function() {
        return "a string value";
    },
    getNumberValue: function() {
        return 20;
    }
};

// 處理數據的中間件
function middleware(value: string): string {
    return value;
}
// 沒問題
middleware(service.getStringValue());
// 報錯:參數類型不對
middleware(service.getNumberValue());
// 設成 any 行不行?
function middleware(value: any): any
// 多寫幾個 middleware 行不行?
function middleware1(value: string): string { ... }
function middleware2(value: number): number { ... }

// 改成泛型
function middleware<T>(value: T): T {
    return value;
}
// 使用
middleware<string>(service.getStringValue());
middleware<number>(service.getNumberValue());

更多內容查看文章:泛型

六、xxx.d.ts聲明文件

若是把 add.js 發佈成一個 npm 包,別人在使用的時候 IDE 的提示每每都不太友好:

clipboard.png

那麼如何能讓 IDE 給 add 方法加上類型提示和參數提示呢?

TypeScript 提供了 xxx.d.ts 文件來給 IDE 使用。

xxx.d.ts 叫作聲明文件,通常用於類型提示和模塊補充,能夠自動生成,也能夠手動編寫,通常狀況下是不用咱們手動編寫的。

// 加上 -d 參數,就會生成 add.d.ts 文件
$ tsc -d add.ts

clipboard.png

// add.d.ts
declare function add(x: number, y: number): number;
export default add;

declare 關鍵字
用於聲明你須要的變量、函數、類等,聲明之後 IDE 就能夠根據它來進行類型提示。

若是有 add.d.ts 文件,那麼它就承擔了類型提示的任務:

clipboard.png

同時也至關於 add 方法的接口文檔:

clipboard.png

手動建立聲明文件

有如下兩個場景會去手動建立這個文件:

  1. 給未使用 TypeScript 的 js 模塊編寫聲明文件,能夠當作交接文檔用;使用別人沒有寫聲明文件的 js 庫。
  2. 模塊補充。
// 定義模塊補充的語法
declare module 'filePath' {
   
}

module-augmentation

使用場景:vue-shim.d.ts 文件,爲了讓 TypeScript 識別 .vue 文件。

// 文件內容
declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}

加強類型以配合插件使用

七、配置文件

冗長的參數

指定輸出目錄:

$ tsc -d --strict -m ESNext --outDir lib index.ts

tsc 的全部參數能夠經過執行:tsc -h來查看。

使用 tsconfig.json 配置文件

避免書寫冗長的命令,豐富的配置項。

生成 tsconfig.json 文件:

$ tsc --init

經常使用配置項解讀:

{
  "compilerOptions": {
    "target": "ES5", /* target用於指定編譯以後的版本目標 version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "ESNext", /* 用來指定要使用的模塊標準: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "declaration": true,  /* 生成對應的 '.d.ts' 聲明文件 */
    "outDir": "./lib", /* 指定編譯輸出目錄 */
    "esModuleInterop": true, /* 經過爲導入內容建立命名空間,實現CommonJS和ES模塊之間的互操做性 */
    "experimentalDecorators": true, /* 啓用 ES7 裝飾器語法 */
  }
}

查看更多配置項

配置後,執行如下命令,就會讀取配置文件:

$ tsc add.ts

八、使用 TypeScript 開發 web 應用

上述的方法適用於開發命令行工具,好比 @xiyun/cli,開發 web 應用仍是不太方便。

若是想要方便地開發 web 應用,好比:開啓前端服務、模塊熱加載等,就須要配合構建工具使用。

配合 webpack 開發 web 應用

點擊查看配合 webpack 開發 web 應用源碼

配合 parcel,零配置開發 web 應用

parcel:極速零配置 Web 應用打包工具。

聯想:能夠當作是已經配置好了的 webpack。

全局安裝 parcel 工具:

$ yarn global add parcel-bundler

運行:

$ parcel index.html

它就會幫你生成 package.json,自動安裝 typeScript,啓動好開發服務,並支持熱加載。

九、在 Vue 和 React 項目中使用

讓 Vue 支持 TypeScript

最方便快捷:vue create my-app,選擇 TypeScript。

若是要改造現有項目:

  1. 增長 dev 依賴包:
"devDependencies": {
    "@vue/cli-plugin-typescript": "^3.8.0",
    "typescript": "^3.4.3",
  }
  1. 增長 tsconfig.json 配置
  2. 更改文件後綴:main.js --> main.ts
  3. 組件中指定 lang:
<script lang="ts">
</script>
  1. 在 src 目錄下加上:vue-shim.d.ts 文件,爲了讓 TypeScript 識別 .vue 文件。
// 文件內容
declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}

Vue 支持 TypeScript 的配置參考

另一種語法

若是想要用這種語法來寫 Vue 組件:

import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}

那麼須要加上這兩個依賴:

"dependencies": {
    // 官網出的ts支持包
    "vue-class-component": "^7.0.2",
    // 對ts支持包更好封裝的裝飾器
    "vue-property-decorator": "^8.1.0"
  },

Vue 官方對 TypeScript 的說明

建立支持 TypeScript 的 React 項目

建立應用時加上--typescript參數便可:

$ create-react-app my-app --typescript

參考資料

TypeScript 官方文檔
TypeScript Declare Keyword
Vue 對 模塊補充的說明

相關文章
相關標籤/搜索