相信常常关注前端技术的同窗对 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.