类型系统

众所周知 JS 是一门 弱类型语言,不到执行时都不能确定变量的类型。编码时可以随心所欲反正不报错,一不小心就写出八哥( undefined 警告!)。

1. 静态类型检查

静态类型检查让 TS 在编辑器中披上 强类型语言 的“马甲”,使得开发者在 编码时 就可以 避免大多数类型错误的情况发生,而开发者要做的就 只是声明变量时多写一个符号和一个单词。

当然你也可以在声明变量时 不指定类型 或者使用 any 类型 来达到 JS 的动态类型效果,让 TypeScript 变成 AnyScript ,任性~

let name: string = '陈皮皮';
name = 9527; // 报错

let age: any = 18;
age = 'eighteen'; // 不报错

真正做到 早发现,早解决,早下班

2. 原始类型

TS 在支持 与 JS 基本相同的原始类型 之外,还额外提供了 枚举(Enum)和元组(Tuple) 的支持。

// 枚举
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) {
    // ...
}


转自:https://forum.cocos.org/t/typescript/93014
@陈皮皮

标签: ts, typescript

添加新评论