從 JavaScript 到 TypeScript - 聲明類型

2021-03-02 SegmentFault

從 JavaScript 語法改寫為 TypeScript 語法,有兩個關鍵點,一點是類成員變量(Field)需要聲明,另一點是要為各種東西(變量、參數、函數/方法等)聲明類型。而這兩個點直接引出了兩個關鍵性的問題,有哪些類型?怎樣聲明?

類型

在說 TypeScript 的類型之前,我們先複習一下 JavaScript 的七種類型:

undefined

function

boolean

number

string

object

symbol

這七種類型都是可以通過 typeof 運算符算出來的,但其中並沒有我們常見的 Array、 null, Date 之類的類型——因為它們其實都是 object。

TypeScript 的重要特性之一就是類型,所以 TypeScript 中的類型要講究得多,除了 JavaScript 中的類型之外,還定義了其它一些(不完全列表)

Array<T>,或 T[],表示 T 類型的數組

null,空類型,其作用與 strictNullChecks 編譯參數有關

Tuple(元組),形如 [Number,String]

enumT,定義枚舉類型 T,可理解為集中對數值常量進行命名

interfaceT,接口, T 是一種接口類型

classT,類, T 是一種類型

any,代表任意類型

void,表示沒有類型,用於聲明函數類型

never,表示函數不可返回的神奇類型

……

具體的類型這裡就不詳述了,官方 Handbook 的 Basic Type、Interfaces、Classes、Enum、Advanced Types這幾部分說得非常清楚。

不過仍然有一種類型相關的特性不得不提——泛型。如果只是說數據類型,純粹的 JSer 們還可以理解,畢竟類型不是新鮮玩意兒,只是擴展了點種類。但是泛型這個東西,純粹的 JSer 們可能就沒啥概念了。

泛型主要是用一個符號來表示一些類型,只要是符合約束條件(默認無約束)的類型,都可以替換掉這個類型符號來使用,比如

function test<T>(v: T) {

   console.log(v);

}

test<boolean>(true);    // 顯式指定 T 由 boolean 替代

test("hello");          // 推斷(隱式) T 被 string 替代

test(123);              // 推斷(隱式) T 被 number 替代

泛型與強類型相關,即需要進行嚴格的類型檢查,又想少寫相似代碼,所以乾脆用某個符號來代替類型。泛型這個名稱本身可能並不是很好理解,但是如果借用 C++ 的「模板」概念,就好理解了。比如上面的泛型函數,根據後面的調用,可以被解釋為三個函數,相當於套用模板,用實際類型代替了 T:

function test(v: boolean) { ... }

function test(v: string) { ... }

function test(v: number) { ... }

關於泛型,更詳細的內容可以參考 Handbook 的 Generic 部分。

類型就簡述到這裡,簡單的類型一看就能明白,高級一點的類型我們以後再開專題來詳述。不過既然選擇使用 TypeScript,必然會用到它的靜態類型特性,那就必須強化識別類型的意識,並養成這樣的習慣。對於純 JSer 來說,這是一個巨大的挑戰。

聲明類型

聲明類型,主要是指聲明變量/常量,函數/方法和類成員的類型。JS 中使用 var 聲明一個變量,ES6 擴展了 let 和 const。這幾種聲明 TypeScript 都支持。要為變量或者常量指定類型也很簡單,就是在變量/常量名後面加個冒號,再指定類型即可,比如

// # typescript

// 聲明函數 pow 是 number 類型,即返回值是 number 類型

// 聲明參數 n 是 number 類型

function pow(n: number): number {

   return n * n;

}

// 聲明 test 是無返回值的

function test(): void {

   for (let i: number = 0; i < 10; i++) {  // 聲明 i 是 number

       console.log(pow(i));

   }

}

這段代碼演示了對函數類型、參數類型和變量類型地聲明。這相對於 JavaScript 代碼來說,似乎變得更複雜了。但是考慮下,如果我們在某處不小心這樣調用了 pow:

// # javascript

let n = "a";

let r = pow(n);     // 這裡存在一個潛在的錯誤

JavaScript 不會提前檢查錯誤的,只有在執行到 r=pow(n) 的時候給 r 賦值為 NaN。然後如果別處又用到 r,可能就會造成連鎖錯誤,可能很要調試一陣才把問題找得出來。

不過上面兩行代碼在 TypeScript 裡是通不過轉譯的,它會報告一個類型不匹配的錯誤:

Argument of type 'string' is not assignable to parameter of type 'number'.

聲明類成員

這時先來看一段 JavaScript 代碼

// # javascript (es6)

class Person {

   constructor(name) {

       this._name = name;

   }

   get name() {

       return this._name;

   }

}

這段 JavaScript 代碼如果翻譯成 TypeScript 代碼,會是這樣

// # typescript

class Person {

   private _name: string;

   public constructor(name: string) {

       this._name = name;

   }

   public get name(): string {

       return this._name;

   }

}

注意到 private_name:string,這句話是在聲明類成員變量 _name。JavaScript 裡是不需要聲明的,對 this._name 賦值,它自然就有了,但在 TypeScript 裡如果不聲明,就會報告屬性不存在的錯誤:

Property '_name' does not exist on type 'Person'.

雖然寫起來麻煩了一點,但是我也能理解 TypeScript 的苦衷。如果沒有這些聲明,tsc 就搞不清楚你在使用 obj.xxxx 或者 this.xxxx 的時候,這個 xxxx 到底確實是你想要添加的屬性名稱呢,還是你不小心寫錯了的呢?

另外要注意到的是 private 和 public 修飾符。JavaScript 中存在私有成員,為了實現私有,大家都想了不少辦法,比如閉包。

TypeScript 提供了 private 來修飾私有成員, protected 修改保護(子類可用)成員, public 修飾公共成員。如果不添加修飾符,默認作為 public,以兼容 JavaScript 的類成員定義。不過特別需要注意的是,這些修飾符只在 TypeScript 環境(比如轉譯過程)有效,轉譯成 JavaScript 之後,仍然所有成員都是公共訪問權限的。比如上例中的 TypeScript 代碼轉譯出來基本上就是之前的 JavaScript 代碼,其 _name 屬性在外部仍可訪問。

當然在 TypeScript 代碼中,如果外部訪問了 _name,tsc 是會報告錯誤的

Property '_name' is private and only accessible within class 'Person'.

所以應用內使用 private 完全沒問題,但是如果你寫的東西需要做為第三方庫發布,那就要想一些手段來進行「私有化」了,其手段和 JavaScript 並沒什麼不同。

小結

從 JavaScript 語法改寫 TypeScript 語法,我們來做個簡單的總結:

類成員需要聲明。

變量、函數參數和返回值需要申明類型。

如果所有這些東西都要聲明類型,工作量還是滿大的,所以我建議:就接口部分聲明類型。也就是說,類成員、函數/方法的參數和返回類型要聲明類型,便於編輯器進行語法提示,局部使用的變量或者箭頭函數,在能明確推導出其類型的時候,可以不聲明類型。

Tip

TypeScript 已更新,回覆:TypeScript 查看相關合集。

相關焦點

  • [譯] JavaScript與TypeScript中的Symbols
    [譯] JavaScript與TypeScript中的Symbols原文連結 https://fettblog.eu/symbols-in-javascript-and-typescript/Symbol是一個JavaScript與TypeScript內建的數據類型. Symbol與其他數據類型相比, 能夠作為對象的屬性鍵值來使用.
  • 你知道vue項目怎麼使用TypeScript嗎?
    這樣會大大提升代碼的可閱讀性② 靜態類型檢查:靜態類型檢查可以避免很多不必要的錯誤,不用在調試的時候才發現問題。下面我們就來一起從構建一個vue+ts的項目開始初始化項目初始化vue-cli項目,安裝typescript,ts-loader,tslint,tslint-loader,tslint-config-standard,vue-property-decorator.上面只有typescript,ts-loader
  • JavaScript vs. TypeScript vs. ReasonML
    靜態類型的優缺點優點:文檔:對於大多數代碼,記錄參數類型是非常有用的,這樣可以方便區分調用者和被調者。好處遠不止這些。當我重新訪問舊的JavaScript代碼庫來添加靜態類型時,我可能已經忘了它是如何工作的了。有了這些類型,可以更清楚地說明一切是如何運行的。
  • TKoa 1.0.1 發布,TypeScript 版的 Node.js Koa 框架
    更新信息: npm增加編譯好的 javascript 文件T-Koa 介紹
  • 我為什麼要將Typescript與Express、nodejs一起使用(譯文)
    在我的職業生涯開始時,我是一名桌面應用開發人員,其中強類型語言佔據了市場主導地位。當我遷移到Web開發時,我對JavaScript和Python等語言的每個新功能都很著迷。事實上,我沒有必要聲明變量的類型,這極大的提高了我的生產力,並且使我的工作變得更有趣了。
  • typescript中的class和interface
    typescript這個東西說實在的,真的是容易忘記,一段時間不用就感覺特別陌生,但是回過頭來看看,又有一種熟悉的感覺,有句話這麼說的ts越用越香,它確實能夠規範我們的書寫的格式,語法校驗和類型校驗等。
  • 受夠了JavaScript的小毛病,我將整個應用移植到了TypeScript
    如果你不熟悉 TypeScript 的函數類型語法,可以在我們的課程中全面了解 TypeScript 的函數類型:https://www.executeprogram.com/courses/typescript/lessons/function-typestype ButtonProps = {
  • JavaScript和TypeScript中的symbol[每日前端夜話0xC0]
    TypeScript中的符號TypeScript 完全支持符號,它是類型系統中的主要成員。symbol 本身是所有可能符號的數據類型注釋。請參閱前面的 extendObject 函數。unique symbol 與聲明緊密相關,只允許在 const 聲明中引用這個確切的符號。你可以將 TypeScript 中的名義類型視為 JavaScript 中的名義值。要獲得 unique symbol 的類型,你需要使用 typeof 運算符。
  • 最詳細從零開始配置 TypeScript 項目的教程
    談談你對 TypeScript 聲明文件的理解?在製作庫包時如何對外識別聲明文件?在外部使用時有哪些好處?在製作工具包的時候如何考慮按需引入和全量引入的優雅引入設計?了解 Vue CLI 3.x 的功能特點嗎?如何基於 Vue CLI 3.x 定製符合團隊項目的腳手架?工程化配置領域的設計可以有哪些設計階段(例如 react-scripts 和 vue ui 在設計以及使用形態上的區別)?
  • 最詳細的從零開始配置 TypeScript 項目的教程
    談談你對 TypeScript 聲明文件的理解?在製作庫包時如何對外識別聲明文件?在外部使用時有哪些好處?在製作工具包的時候如何考慮按需引入和全量引入的優雅引入設計?了解 Vue CLI 3.x 的功能特點嗎?如何基於 Vue CLI 3.x 定製符合團隊項目的腳手架?工程化配置領域的設計可以有哪些設計階段(例如 react-scripts 和 vue ui 在設計以及使用形態上的區別)?
  • 或許是網上最詳細從零開始配置 TypeScript 項目的教程
    談談你對 TypeScript 聲明文件的理解?在製作庫包時如何對外識別聲明文件?在外部使用時有哪些好處?在製作工具包的時候如何考慮按需引入和全量引入的優雅引入設計?了解 Vue CLI 3.x 的功能特點嗎?如何基於 Vue CLI 3.x 定製符合團隊項目的腳手架?工程化配置領域的設計可以有哪些設計階段(例如 react-scripts 和 vue ui 在設計以及使用形態上的區別)?
  • 通俗易懂的 TYPESCRIPT 入門教程
    ,對於剛入門 TypeScript 的小夥伴,也可以不用安裝 typescript,而是直接使用線上的 TypeScript Playground 來學習新的語法或新特性。;六、交叉類型TypeScript 交叉類型是將多個類型合併為一個類型。 這讓我們可以把現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。
  • javascript之常用數據類型及判斷方法
    前端工作者學習之路對於剛開始入門的前端人員來說,javascript中的數據類型是既熟悉又籠統的概念,不論在php,還是c語言抑或java,他們的數據類型都各不相同,大家也千萬不用混淆,今天,我們來具體重新了解下javascript中的常見數據類型以及他們的判斷方法。
  • 七天學會javascript第一天javascript介紹
    javascript介紹javascript數據類型javascript運算符javascript對象javascriptjavascript :客戶端編程。javascript是由客戶端去解釋運行的。怎麼引入javascript 呢?
  • TypeScript 3.6 發布,微軟腳本程式語言
    這要歸功於因在Iterator和IteratorResult類型聲明中進行的一些更改而引入部分新的類型參數,以及 TypeScript 用於代表稱為Generator類型的生成器。該版本中 Iterator 類型允許用戶說明 yield 類型、返回的類型和 next 可以接受的類型。
  • 從 React 遷移到 TypeScript:忍受了 15 年的 JavaScript 錯誤從此走遠
    (這裡刪除了一些不相關的細節;本系列文章中所有涉及到錯誤的地方都會這樣處理。)src/client/components/explanation.tsx(13,27):  error: Type 'number' is not assignable to type 'boolean | undefined'.
  • Vue2.5+ Typescript 引入全面指南
    總原則兩大原則:最小依賴引入由於我個人從Javascript到Typescript的升級,更傾向於平滑順移,因此,我對新依賴的引入整體保持克制原則,只引入了必要項,以儘量貼近原生vue寫法:以下依賴均未引入:1. vue-class-component
  • ...發布,TypeScript 版的 Node.js Koa 框架 - OSCHINA - 中文開源...
    Tkoa是使用 typescript 編寫的 koa 框架! 儘管它是基於 typescript 編寫,但是你依然還是可以使用一些 node.js 框架和基於 koa 的中間件。不僅如此,你還可以享受 typescript 的類型檢查系統和方便地使用 typescript 進行測試!安裝TKoa 需要 >= typescript v3.1.0 和 node v7.6.0 版本。
  • TypeScript 中文入門教程
    鑑於我的博客的影響力(羞),比如在百度上搜索: typescript 入門,我之前的文章是結果的第三個(2015-12-03測試),所以我有必要將這些翻譯 Copy 到我的博客,然後整理一下發表到博客園主頁,當然我會註明這不是我翻譯的,註明出處,這樣至少通過我的博客可以跳轉到原始的翻譯。於是我繼續下去了。
  • 實現一個 async/await (typescript 版)
    好了,不多說,接上篇 實現一個符合 Promise/A+規範的 Promise(typescript 版)。這次我們來實現一個 typescript 版本的 async/await。 可以看到,還是可以通過該泛型的類型為我們提供提示的,但是這樣寫的缺點也應該看到了,特別的麻煩,同時需要自己對三個泛型參數進行定義,並且由於是合併的類型,所以對於類型的提示其實非常的糟糕,最好的做法應該是在函數內部進行變量聲明的時候直接定義類型