相信常常關注前端技術的同窗對 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 的目的是爲了更好地開發大型項目,其做者爲大名鼎鼎的 「C# 之父 Anders Hejlsberg」 。
在 TypeScript 中文主頁中對於 TypeScript 的定義是「「JavaScript 的超集」」, TypeScript 「支持JavaScript 的全部語法語義和最新的 ECMAScript 特性」,而且「額外添加了不少特性」。
經過 TypeScript 編譯器(tsc),TypeScript 代碼能夠被「編譯成純淨、簡潔的 JavaScript 代碼」。
主頁中對 TypeScript 的介紹:
「TypeScript 雖爲大型項目而生,可是不表明它不適用於中小型項目,只是項目越大收益越明顯。」
「TypeScript 彌補了 JavaScript 的許多不足,同時保留了 JavaScript 的靈活性,大大提升了項目的開發效率以及可維護性。」
「TypeScript 的誕生不是爲了取代 JavaScript ,而是讓 JavaScript 變得更好。」
「因此 TypeScript 對於開發者來講,不只僅是一門編程語言,更是生產力工具。」
TypeScript 的良好口碑以及日漸龐大的生態,早就已經證實了它本身。
許多優秀的開源項目例如前端三大框架 「Angular」、「React」 和 「Vue」 均已支持 TypeScript ,「Angular2 和」 「Vue 3.0 都是直接用 TypeScript 開發的」!
大多數第三方 JavaScript 庫都「提供了對 TypeScript 的支持」。
而且 「Node.js」 做者近期正式發佈的 「Deno 1.0」 也是「原生支持 TypeScript」 。
能夠看到 TypeScript 的將來一片光明...
你幾乎每天用來寫代碼的 「VS Code」 也是用 TypeScript 編寫的。(用記事本寫代碼的大佬請先收起你的菜刀)
而對於 Creator 開發者來講「最最最重要」的是:
「Cocos Creator 引擎開發團隊也建議開發者使用 TypeScript 進行開發。」
目前 「Creator 3D 只支持使用 TypeScript」 進行開發。
我能夠大膽的說將來 「TypeScript 將會成爲 Cocos Creator 開發的標配!」
既然 TypeScript 爲大型項目而生,那不如就讓咱們看看 TypeScript 爲何適合大型項目?
在項目的開發中,一定少不了衆多的開發人員,在這個模塊化的時代,「一個項目的多個模塊可能均由不一樣的人來開發,而且每一個人都有不一樣的編碼習慣」。
在使用 JavaScript 進行開發時,因爲「沒有類型限制、自動補全和智能提示」,就須要「開發人員之間的頻繁溝通」或者「頻繁閱讀文檔」(詳細的文檔很關鍵)來保證代碼能夠正確執行。
即使如此,開發人員也不能保證「每一個變量/函數名都一次寫對」,「每一個參數都一次傳對」。
這些溝通和翻閱文檔所花費的時間都在默默「下降項目的總體開發效率」。
而使用 TypeScript 進行開發時,得益於「類型系統」,在讀取變量或調用函數時,均有「自動補全」,「基本杜絕寫錯變量/函數名的狀況」。
「類型限制」與「智能提示」讓開發人員調用 API 時能夠「快速得知參數要求」,不須要再頻繁閱讀代碼、文檔或詢問模塊開發者。
「全部變量、函數和類均可以快速溯源(跳轉到定義)」,讓 TypeScript 代碼有着「較好的可維護性」。合理利用註釋甚至能夠徹底不看文檔,真正作到「註釋即文檔」(文檔仍是要有的 : p)。
總之就是「開發效率 +++ !」
衆所周知 JS 是一門「弱類型語言」,不到執行時都不能肯定變量的類型。編碼時能夠爲所欲爲反正不報錯,一不當心就寫出八哥( undefined 警告!)。
靜態類型檢查讓 TS 在編輯器中披上「強類型語言」的「馬甲」,使得開發者在「編碼時」就能夠「避免大多數類型錯誤的狀況發生」,而開發者要作的就「只是聲明變量時多寫一個符號和一個單詞」。
固然你也能夠在聲明變量時「不指定類型」或者使用 「any 類型」來達到 JS 的動態類型效果,讓 「Type」Script 變成 「Any」Script ,任性~
let name: string = '陳皮皮';
name = 9527; // 報錯 let age: any = 18; age = 'eighteen'; // 不報錯 複製代碼
真正作到 「早發現,早解決,早下班」
![]()
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']; // 報錯 複製代碼
「類型系統」配合「聲明文件」(關於聲明文件咱們後面再聊)給咱們帶來了編輯器中「完善的自動補全智能提示」,大大增長了開發效率,也再不會由於拼錯變量名或函數名而致使運行時的錯誤。
我知道 JS 加插件也能實現必定程度的智能提示可是語言自帶它不香嗎 : )
![]()
淚目,是從 C# 那裏幾乎原汁原味搬過來的一套修飾符和關鍵字,主要如如下幾個:
用來「限定類成員的可訪問範圍」。
沒有 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'; // 能夠訪問 } } 複製代碼
用於「定義全局惟一的靜態變量和靜態函數」。
在 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 複製代碼
用來定義「抽象類或抽象函數」,面向對象編程很重要的一環。
沒對象的能夠面向工資編程...
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 { // 報錯了,沒有實現進食的功能 } 複製代碼
用來定義只讀的字段,使得字段「只能在建立的時候賦值一次」。
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; // 報錯,身份證號碼一旦肯定不能更改 複製代碼
C# 和 Java 的朋友們讓我看到大家的雙手好嗎
「接口用於一系列成員的聲明,但不包含實現,接口支持合併(重複聲明),也能夠繼承於另外一接口。」
下面展現幾個常見的用法:
// 擴展 String 類型
interface String { /** * 翻譯 */ translate(): string; } // 實現翻譯函數 String.prototype.translate = function () { return this; // 不具體寫了,直接返回原字符串吧 }; // 使用 let nickname = '陳皮皮'.translate(); 複製代碼
interface Human {
name: string; // 普通屬性,必須有可是能夠改 readonly id: number; // 只讀屬性,一旦肯定就不能更改 hair?: number; // 可選屬性,挺禿然的 } let ChenPiPi: Human = { name: '陳皮皮', id: 123456789, hair: 9999999999999 } 複製代碼
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 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, } 複製代碼
使用「聯合類型」容許你在「聲明變量或接收參數時兼容多種類型」。
我的最喜歡的特性之一,點贊!
let bye: string | number;
bye = 886; // 不報錯 bye = 'bye'; // 不報錯 bye = false; // 報錯 複製代碼
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' 複製代碼
C# 和 Java 的朋友們再次讓我看到大家的雙手好嗎
使用「泛型」可讓一個「類/函數支持多種類型的數據,使用時能夠傳入須要的類型」。
又是一個很是實用的特性,利用泛型能夠「大大增長代碼的可重用性,減小重複的工做」,點贊!
如下是兩個經常使用的用法:
// 這是一個清洗物品的函數
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); 複製代碼
// 盒子
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 類型 複製代碼
這是一個相對比較高級的特性,「以 @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; } 複製代碼
「命名空間用來定義標識符的可用範圍,主要用於解決重名的問題,對於項目模塊化有很大的幫助。」
「Cocos Creator 中的 cc 就是一個內置的命名空間。」
// 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(); // 我是弟弟! 複製代碼
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) { // ... } 複製代碼
「聲明文件,即以 d.ts 做爲後綴的代碼文件,用來聲明當前環境中可用的類型。」
聲明文件這一特性對於 TypeScript 來講是「極其重要」的,代碼編輯器中的智能提示等特性都依賴於聲明文件。
能夠發現目前大多數「第三方 JavaScript 庫」都有聲明文件,聲明文件讓這些庫在代碼編輯器中也能夠「擁有類型檢查智能提示等特性」,使用體驗 Max 。
咱們甚至能夠「聲明一些環境中不存在的類型」,例如我在《微信小遊戲接入好友排行榜》這篇文章中編寫的 wx.d.ts 文件,使得我在編輯器環境中調用根本不存在的 wx 函數時不會報錯且有智能提示。
通常 Cocos Creator 項目的根目錄下都有一個聲明文件 「creator.d.ts」 ,文件中聲明瞭 Cocos Creator 引擎幾乎全部可用的 API 。因此即便是純 JavaScript 的 Creator 項目,使用 cc 命名空間時也有智能提示。
在 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 }); 複製代碼
新建項目時,在項目模板中選擇 「Hello TypeScript」 ,就能夠建立一個含有 TypeScript 相關配置和基本組件的項目。
想要在原有的 JavaScript Creator 項目中使用 TypeScript ,須要點擊編輯器上方主菜單的 「[開發者 -> VS Code 工做流 -> 更新 VS Code 智能提示數據]」 和 「[開發者 -> VS Code 工做流 -> 添加 TypeScript 項目配置]」 來給項目添加 「creator.d.ts」 聲明文件和 「tsconfig.json」 配置文件。
在 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
我是陳皮皮,這是個人我的公衆號,專一但不只限於遊戲開發、前端和後端技術記錄與分享。
每一篇原創都很是用心,你的關注就是我原創的動力!
Input and output.