为何选择 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.

相关文章
相关标签/搜索