爲何選擇 TypeScript

前言

相信常常關注前端技術的同窗對 TypeScript 應該不陌生,或多或少看過一些關於 TypeScript 的文章。html

各類技術論壇上也有很多關於 TypeScript 和 JavaScript 的討論,大多數人對 TypeScript 都有着不錯評價,但也有很多人以爲它沒有存在的必要。前端

事實上,TypeScript 做爲前端編程語言界的當紅炸子雞,配合代碼編輯器界的當紅炸子雞 VS Code 一塊兒食用,可以讓人擁有極佳的用餐哦不編碼體驗。git

許多過去一直使用 JavaScript 的同窗,在使用 TypeScript 以後,都以爲再也回不去了。微軟的這一套組合拳,打得多少人大喊真香!(真香定律雖遲但到)es6

它不是好很差用的問題,它真的是那種,那種不多見的那種...... github

魯迅先生曾說過:人生苦短,我用 TS 。 web

回到正題

做爲假前端的我,使用 TypeScript 進行開發也有近兩年的時間了,也但願和各位分享一下個人見解。typescript

因此在本篇文章我將以一名 Cocos Creator 開發者的角度,來對 TypeScript 作一波客觀分析(強行安利),但願對各位有所幫助。express


大綱

1. 什麼是 TypeScript編程

2. TypeScript 存在的意義json

3. TypeScript 帶來了什麼改變

4. TypeScript 有什麼特性

5. Cocos Creator 中 TS 和 JS 在使用上的區別

6. 如何建立 Cocos Creator TS 項目

7. 原有的 JS 項目如何使用 TS


正文

什麼是 TypeScript

TypeScript 是一種由微軟開發並開源的跨平臺編程語言,最初開發 TypeScript 的目的是爲了更好地開發大型項目,其做者爲大名鼎鼎的 C# 之父 Anders Hejlsberg

在 TypeScript 中文主頁中對於 TypeScript 的定義是「JavaScript 的超集」, TypeScript 支持JavaScript 的全部語法語義和最新的 ECMAScript 特性,而且額外添加了不少特性

經過 TypeScript 編譯器(tsc),TypeScript 代碼能夠被編譯成純淨、簡潔的 JavaScript 代碼

主頁中對 TypeScript 的介紹:


TypeScript 存在的意義

生產力工具

TypeScript 雖爲大型項目而生,可是不表明它不適用於中小型項目,只是項目越大收益越明顯。

TypeScript 彌補了 JavaScript 的許多不足,同時保留了 JavaScript 的靈活性,大大提升了項目的開發效率以及可維護性。

TypeScript 的誕生不是爲了取代 JavaScript ,而是讓 JavaScript 變得更好。

因此 TypeScript 對於開發者來講,不只僅是一門編程語言,更是生產力工具。

前途大好

TypeScript 的良好口碑以及日漸龐大的生態,早就已經證實了它本身。

許多優秀的開源項目例如前端三大框架 AngularReactVue 均已支持 TypeScript ,Angular2 和 Vue 3.0 都是直接用 TypeScript 開發的

大多數第三方 JavaScript 庫都提供了對 TypeScript 的支持

而且 Node.js 做者近期正式發佈的 Deno 1.0 也是原生支持 TypeScript

能夠看到 TypeScript 的將來一片光明...

你幾乎每天用來寫代碼的 VS Code 也是用 TypeScript 編寫的。(用記事本寫代碼的大佬請先收起你的菜刀)

Cocos Creator

而對於 Creator 開發者來講最最最重要的是:

Cocos Creator 引擎開發團隊也建議開發者使用 TypeScript 進行開發。

目前 Creator 3D 只支持使用 TypeScript 進行開發。

我能夠大膽的說將來 TypeScript 將會成爲 Cocos Creator 開發的標配!


TypeScript 帶來了什麼改變

既然 TypeScript 爲大型項目而生,那不如就讓咱們看看 TypeScript 爲何適合大型項目?

在項目的開發中,一定少不了衆多的開發人員,在這個模塊化的時代,一個項目的多個模塊可能均由不一樣的人來開發,而且每一個人都有不一樣的編碼習慣

在使用 JavaScript 進行開發時,因爲沒有類型限制、自動補全和智能提示,就須要開發人員之間的頻繁溝通或者頻繁閱讀文檔(詳細的文檔很關鍵)來保證代碼能夠正確執行。

即使如此,開發人員也不能保證每一個變量/函數名都一次寫對每一個參數都一次傳對

這些溝通和翻閱文檔所花費的時間都在默默下降項目的總體開發效率

而使用 TypeScript 進行開發時,得益於類型系統,在讀取變量或調用函數時,均有自動補全基本杜絕寫錯變量/函數名的狀況

類型限制智能提示讓開發人員調用 API 時能夠快速得知參數要求,不須要再頻繁閱讀代碼、文檔或詢問模塊開發者。

全部變量、函數和類均可以快速溯源(跳轉到定義),讓 TypeScript 代碼有着較好的可維護性。合理利用註釋甚至能夠徹底不看文檔,真正作到「註釋即文檔」(文檔仍是要有的 : p)。

總之就是開發效率 +++ !


TypeScript 的特性

類型系統

衆所周知 JS 是一門弱類型語言,不到執行時都不能肯定變量的類型。編碼時能夠爲所欲爲反正不報錯,一不當心就寫出八哥( undefined 警告!)。

1. 靜態類型檢查

靜態類型檢查讓 TS 在編輯器中披上強類型語言的「馬甲」,使得開發者在編碼時就能夠避免大多數類型錯誤的狀況發生,而開發者要作的就只是聲明變量時多寫一個符號和一個單詞

固然你也能夠在聲明變量時不指定類型或者使用 any 類型來達到 JS 的動態類型效果,讓 TypeScript 變成 AnyScript ,任性~

let name: string = '陳皮皮';
name = 9527; // 報錯  let age: any = 18; age = 'eighteen'; // 不報錯 複製代碼

真正作到 早發現,早解決,早下班

2. 原始類型

TS 在支持與 JS 基本相同的原始類型以外,還額外提供了**枚舉(Enum)和元組(Tuple)**的支持。

Cocos Creator 用戶狂喜:不再須要 cc.Enum 了 ; p

// 枚舉
enum Direction {  Up = 1,  Down,  Left,  Right } let direction: Direction = Direction.Up;  // 元組 let x: [string, number]; x = ['hello', 10]; // 不報錯 x = [10, 'hello']; // 報錯 複製代碼

3. 智能提示

類型系統配合聲明文件(關於聲明文件咱們後面再聊)給咱們帶來了編輯器中完善的自動補全智能提示,大大增長了開發效率,也再不會由於拼錯變量名或函數名而致使運行時的錯誤。

我知道 JS 加插件也能實現必定程度的智能提示可是語言自帶它不香嗎 : )

真香警告

修飾符和靜態關鍵字

淚目,是從 C# 那裏幾乎原汁原味搬過來的一套修飾符和關鍵字,主要如如下幾個:

1. 訪問修飾符(public、private 和 protected)

用來限定類成員的可訪問範圍

沒有 internal 和 protect internal

沒有訪問修飾符的封裝莫得靈魂!

class Me {
 public name = '陳皮皮'; // 你們都知道我叫陳皮皮  private secret = '*******'; // 個人祕密只有我知道  protected password = '********'; // 個人支付寶密碼會告訴個人後人的 }  let me = new Me(); let a = me.name; // 拿到了個人名字 let b = me.secret; // 報錯,私有的屬性 let c = me.password; // 報錯,受保護的屬性  class Child extends Me {  constructor() {  super();  this.name = '陳XX';  this.secret // 報錯,沒法訪問  this.password = '888888'; // 能夠訪問  } } 複製代碼

2. 靜態關鍵字(static)

用於定義全局惟一的靜態變量和靜態函數

在 Creator 的 JS 腳本中是使用 cc.Class 的 statics 屬性來定義靜態成員的,使用體驗一言難盡...

另外在 ES6 中 JS 已經支持靜態函數,在 ES7 中也加入了對靜態屬性的支持。

class Whatever {
 public static origin: string = 'Whatever';  public static printOrigin() {  console.log(this.origin);  console.log(Whatever.origin);  }; }  console.log(Whatever.origin); // Whatever Whatever.printOrigin(); // Whatever 複製代碼

3. 抽象關鍵字(abstract)

用來定義抽象類或抽象函數,面向對象編程很重要的一環。

沒對象的能夠面向工資編程...

abstract class Animal {
 abstract eat(): void; // 不一樣動物進食的方式不同 }  let animal = new Animal(); // 報錯,法實例化抽象類無  class Dog implements Animal {  eat() {  console.log('我吃,汪!');  } }  let dog = new Dog(); dog.eat(); // 我吃,汪!  class Cat implements Animal {  // 報錯了,沒有實現進食的功能 } 複製代碼

4. 只讀關鍵字(readonly)

用來定義只讀的字段,使得字段只能在建立的時候賦值一次

class Human {
 name: string;  readonly id: number;  constructor(name: string, id: number) {  this.name = name;  this.id = id;  } }  let human = new Human('陳皮皮', 666666); human.name = '陳不皮'; // 名字能夠改 human.id = 999999; // 報錯,身份證號碼一旦肯定不能更改 複製代碼

接口(Interface)

C# 和 Java 的朋友們讓我看到大家的雙手好嗎

接口用於一系列成員的聲明,但不包含實現,接口支持合併(重複聲明),也能夠繼承於另外一接口。

下面展現幾個常見的用法:

1. 擴展原始類型

// 擴展 String 類型
interface String {   /**  * 翻譯  */  translate(): string;  }  // 實現翻譯函數 String.prototype.translate = function () {  return this; // 不具體寫了,直接返回原字符串吧 };  // 使用 let nickname = '陳皮皮'.translate(); 複製代碼

2. 定義類型

interface Human {
 name: string; // 普通屬性,必須有可是能夠改  readonly id: number; // 只讀屬性,一旦肯定就不能更改  hair?: number; // 可選屬性,挺禿然的 }  let ChenPiPi: Human = {  name: '陳皮皮',  id: 123456789,  hair: 9999999999999 } 複製代碼

3. 類實現接口

interface Vehicle {
 wheel: number;  engine?: string;  run(): void; }  class Car implements Vehicle {  wheel: 4;  engine: '帝皇引擎';  run() {  console.log('小汽車跑得快!')  } }  class Bike implements Vehicle {  wheel: 2;  run() {  console.log('小黃車沖沖衝!')  } } 複製代碼

類型別名(Type)

這是一個比較經常使用的特性,做用如其名。

類型別名用來給類型起一個新的名字

類型別名和接口很類似,類型別名能夠做用於原始類型,聯合類型,元組以及其它任何你須要手寫的類型,接口支持合併而類型別名不能夠。

類型別名一樣也支持擴展,而且能夠和接口互相擴展。

// 給原始類型起個小名
type UserName = string; let userName: UserName = '陳皮';  // 還能夠是函數 type GetString = () => string; let getString: GetString = () => {  return 'i am string'; } let result = getString();  // 建立一個新的類型 type Name = {  realname: string;  nickname: string; } let name: Name = {  realname: '吳彥祖',  nickname: '陳皮皮' } // 再來一個新的類型 type Age = {  age: number; } // 用上面兩個類型擴展出新的類型 type User = Name & Age; let user: User = {  realname: '吳彥祖',  nickname: '陳皮皮',  age: 18, } 複製代碼

聯合類型(Union Types)

使用聯合類型容許你在聲明變量或接收參數時兼容多種類型

我的最喜歡的特性之一,點贊!

1. 表示一個值能夠是幾種類型之一

let bye: string | number;
bye = 886; // 不報錯 bye = 'bye'; // 不報錯 bye = false; // 報錯 複製代碼

2. 讓函數接受不一樣類型的參數,並在函數內部作不一樣處理

function padLeft(value: string, padding: string | number) {
 if (typeof padding === 'string') {  return padding + value;  } else {  return Array(padding + 1).join('') + value;  } }  padLeft('Hello world', 4); // 返回 ' Hello world' padLeft('Hello', 'I said: '); // 返回 'I said: Hello' 複製代碼

泛型(Generics)

C# 和 Java 的朋友們再次讓我看到大家的雙手好嗎

使用泛型可讓一個類/函數支持多種類型的數據,使用時能夠傳入須要的類型

又是一個很是實用的特性,利用泛型能夠大大增長代碼的可重用性,減小重複的工做,點贊!

如下是兩個經常使用的用法:

1. 泛型函數

// 這是一個清洗物品的函數
function wash<T>(item: T): T {  // 僞裝有清洗的邏輯...  return item; }  class Dish { } // 這是盤子 let dish = new Dish(); // 來個盤子 // 盤子洗完仍是盤子 // 用尖括號提早告訴它這是盤子 dish = wash<Dish>(dish);  class Car { } // 這是汽車 let car = new Car(); // 買輛汽車 // 汽車洗完仍是汽車 // 沒告訴它這是汽車可是它認出來了 car = wash(car); 複製代碼

2. 泛型類

// 盒子
class Box<T>{  item: T = null;  put(value: T) {  this.item = value;  }  get() {  return this.item;  } }  let stringBox = new Box<String>(); // 買一個用來裝 String 的盒子 stringBox.put('你好!'); // 存一個 '你好!' // stringBox.put(666); // 報錯,只能存 String 類型的東西 let string = stringBox.get(); // 拿出來的是 String 類型 複製代碼

裝飾器(Decorator)

這是一個相對比較高級的特性,以 @expression 的形式對類、函數、訪問符、屬性或參數進行額外的聲明

利用裝飾器能夠作不少騷操做,感興趣的話能夠深刻研究下。

對類作預處理

export function color(color: string) {
 return function (target: Function) {  target.prototype.color = color;  } }  @color('white') class Cloth {  color: string; } let cloth = new Cloth(); console.log(cloth.color); // white  @color('red') class Car {  color: string; } let car = new Car(); console.log(car.color); // red 複製代碼

Creator 中的 TS 組件中的 ccclass 和 property 就是兩個裝飾器

const { ccclass, property } = cc._decorator;
 @ccclass export default class CPP extends cc.Component {   @property(cc.Node)  private abc: cc.Node = null;  } 複製代碼

命名空間(namespace)

命名空間用來定義標識符的可用範圍,主要用於解決重名的問題,對於項目模塊化有很大的幫助。

Cocos Creator 中的 cc 就是一個內置的命名空間。

1. 對相同名字的類和函數進行區分

// pp 命名空間
namespace pp {  export class Action {  public static speak() {  cc.log('我是皮皮!');  }  } }  // dd 命名空間 namespace dd {  export class Action {  public static speak() {  cc.log('我是弟弟!');  }  } }  // 使用 pp.Action.speak(); // 我是皮皮! dd.Action.speak(); // 我是弟弟! 複製代碼

2. 對接口進行分類

namespace Lobby {
 export interface Request {  event: string,  other: object  // ...  } }  namespace Game {  export interface Request {  event: string,  status: string  // ...  } }  // 用於 Lobby 的請求函數 function requestLobby(request: Lobby.Request) {  // ... }  // 用於 Game 的請求函數 function requestGame(request: Game.Request) {  // ... } 複製代碼

聲明文件(Declaration Files)

聲明文件,即以 d.ts 做爲後綴的代碼文件,用來聲明當前環境中可用的類型。

聲明文件這一特性對於 TypeScript 來講是極其重要的,代碼編輯器中的智能提示等特性都依賴於聲明文件。

能夠發現目前大多數第三方 JavaScript 庫都有聲明文件,聲明文件讓這些庫在代碼編輯器中也能夠擁有類型檢查智能提示等特性,使用體驗 Max 。

咱們甚至能夠聲明一些環境中不存在的類型,例如我在《微信小遊戲接入好友排行榜》這篇文章中編寫的 wx.d.ts 文件,使得我在編輯器環境中調用根本不存在的 wx 函數時不會報錯且有智能提示。

通常 Cocos Creator 項目的根目錄下都有一個聲明文件 creator.d.ts ,文件中聲明瞭 Cocos Creator 引擎幾乎全部可用的 API 。因此即便是純 JavaScript 的 Creator 項目,使用 cc 命名空間時也有智能提示。


Creator 中 TS 和 JS 在使用上的區別

聲明組件

在 TypeScript 腳本中 class 的聲明方式 和 ES6 Class 類似,並使用了裝飾器 @ccclass 來將普通 class 聲明成 CCClass :

const { ccclass } = cc._decorator;
 @ccclass export default class Test extends cc.Component {  } 複製代碼

在 JavaScript 腳本中聲明的方式:

cc.Class({
 extends: cc.Component,  }); 複製代碼

聲明屬性

在 TypeScript 腳本中須要使用裝飾器 @property 來聲明屬性,基本類型能夠不傳參數(參數和使用 JavaScript 時基本一致):

const { ccclass, property } = cc._decorator;
 @ccclass export default class Test extends cc.Component {   @property  myNumber: number = 666;   @property  myString: string = '666';   @property  myBoolean: boolean = true;   @property(cc.Node)  myNode: cc.Node = null;   @property([cc.Node])  myNodes: cc.Node[] = [];   @property({  visible: true,  displayName: '位置',  tooltip: '就是一個位置'  })  myVec2: cc.Vec2 = new cc.Vec2();   @property({  type: cc.Sprite,  visible() { return this.myBoolean },  tooltip: '當 myBoolean 爲 true 纔會展現該屬性'  })  mySprite: cc.Sprite = null;   @property  _getset = 0;  @property  get getset() { return this._getset }  set getset(value) { this._getset = value }  } 複製代碼

在 JavaScript 腳本中須要在 properties 中定義屬性(使用時沒有智能提示,就很難受):

cc.Class({
 extends: cc.Component,   properties: {  myNumber: 666,  myString: '666',  myBoolean: true,  myNode: cc.Node,  myNodes: [cc.Node],  myVec2: {  default: new cc.Vec2(),  visible: true,  displayName: '位置',  tooltip: '就是一個位置'  },  mySprite: {  type: cc.Sprite,  default: null,  visible() { return this.myBoolean },  tooltip: '當 myBoolean 爲 true 纔會展現該屬性'  },  _getset: 0,  getset: {  get() { return this._getset; },  set(value) { this._getset = value; }  }  }  }); 複製代碼

導入/導出組件/模塊

在 TypeScript 腳本中使用 ES 模塊的方式來導出或導入組件/模塊:

// A.ts
const { ccclass, property } = cc._decorator;  @ccclass export default class A extends cc.Component {   @property  public nickname: string = 'A';   public greet() {  cc.log('A: greet()');  }  }  // B.ts import A from "./A";  const { ccclass, property } = cc._decorator;  @ccclass export default class B extends cc.Component {   @property(A)  private a: A = null;   onLoad() {  // 訪問實例屬性  let nickname = this.a.nickname;  // 調用實例函數  this.a.greet();  }  } 複製代碼

在 JavaScript 腳本中使用的是 Common JS 模塊的方式(其實 cc.Class 會默認導出,可是 VS Code 識別不了,因此通常都會用 module.export 導出。且使用時也沒有智能提示全靠手打):

// A.js
let A = cc.Class({  extends: cc.Component,   properties: {  nickname: 'A'  },   greet() {  cc.log('A: greet()');  }  });  module.export = A;  // B.js let A = require('./A');  let B = cc.Class({  extends: cc.Component,   properties: {  a: {  type: A,  default: null  }  },   onLoad() {  // 訪問實例屬性  let nickname = this.a.nickname;  // 調用實例函數  this.a.greet();  }  });  module.export = B; 複製代碼

靜態變量/函數

在 TypeScript 腳本中直接使用 static 關鍵字聲明靜態變量和函數:

// A.ts
const { ccclass, property } = cc._decorator;  @ccclass export default class A extends cc.Component {   public static id: number = 999999;   public static staticGreet() {  cc.log('A: staticGreet()');  }  }  // B.ts import A from "./A";  const { ccclass, property } = cc._decorator;  @ccclass export default class B extends cc.Component {   onLoad() {  // 訪問靜態屬性  let id = A.id;  // 調用靜態函數  A.staticGreet();  }  } 複製代碼

在 JavaScript 腳本中使用 static 屬性來定義靜態變量或函數(使用時沒有智能提示全靠手打):

// A.js
let A = cc.Class({  extends: cc.Component,   static: {  id: 999999,   staticGreet() {  cc.log('A: staticGreet()');  }  }  });  module.export = A;  // B.js let A = require('./A');  let B = cc.Class({  extends: cc.Component,   onLoad() {  // 訪問靜態變量  let id = A.id;  // 調用靜態函數  A.staticGreet();  }  });  module.export = B; 複製代碼

枚舉

上面也有說到 TS 自帶枚舉類型,因此在 TS 腳本中能夠直接 enum 來定義枚舉,而在 JS 腳本中須要用 cc.Enum 來定義枚舉。

// TypeScript 腳本的方式
enum Direction {  Up = 1,  Down,  Left,  Right }  // JavaScript 腳本的方式 const Direction = cc.Enum({  Up = 1,  Down,  Left,  Right }); 複製代碼

如何建立 Creator TS 項目

新建項目時,在項目模板中選擇 Hello TypeScript ,就能夠建立一個含有 TypeScript 相關配置和基本組件的項目。


原有的 JS 項目使用 TS

添加配置

想要在原有的 JavaScript Creator 項目中使用 TypeScript ,須要點擊編輯器上方主菜單的 [開發者 -> VS Code 工做流 -> 更新 VS Code 智能提示數據][開發者 -> VS Code 工做流 -> 添加 TypeScript 項目配置] 來給項目添加 creator.d.ts 聲明文件和 tsconfig.json 配置文件。

  • creator.d.ts 是 Cocos Creator 引擎 API 的聲明文件
  • tsconfig.json 是 TypeScript 項目的環境配置文件

混用

在 Creator 項目中添加配置後能夠混用 JS 和 TS 腳本,也能享受到 TS 到來的福利。也就是說原有的 JS 腳本能夠保留,不影響後續添加新的 TS 腳本。

重構

可是若是想要將項目徹底重構爲 TS 項目,要作的就是將原有的 JS 腳本逐個修改成 TS 腳本,並對腳本內的寫法進行轉換。

對於較爲複雜的項目,對項目代碼進行重構這一行爲可能須要花費較長的時間,若是沒有作好充足的準備,不建議着手進行。

可是一旦完成重構,TS 毫不會讓你失望,一定會給項目開發帶來全方位的提高!


相關資料

TypeScript 官網 https://www.typescriptlang.org

TypeScript 中文網 https://www.tslang.cn

TypeScript 開源代碼倉庫 https://github.com/Microsoft/TypeScript

Cocos Creator TypeScript 使用文檔 https://docs.cocos.com/creator/manual/zh/scripting/typescript.html

TypeScript 入門教程 by xcatliu https://github.com/xcatliu/typescript-tutorial

ECMAScript 6 入門 by 阮一峯 https://es6.ruanyifeng.com

awesome-typescript by semlinker https://github.com/semlinker/awesome-typescript


傳送門

微信推文版本

我的博客:菜鳥小棧

開源主頁:陳皮皮

Eazax-CCC 遊戲開發腳手架


更多分享

多平臺通用的屏幕分辨率適配方案

圍繞物體旋轉的方案以及現成的組件

一個全能的挖孔 Shader

一個開源的自動代碼混淆插件

微信小遊戲接入好友排行榜(開放數據域)


公衆號

菜鳥小棧

我是陳皮皮,這是個人我的公衆號,專一但不只限於遊戲開發、前端和後端技術記錄與分享。

每一篇原創都很是用心,你的關注就是我原創的動力!

Input and output.

相關文章
相關標籤/搜索