
面向Unity 开发者的 TypeScript 快速入门笔记
为什么我学 TypeScript
众所周知,游戏热更新技术中经常使用 Lua(ToLua/XLua),但在听说 TypeScript 在热更新上已经开始被应用、并且有腾讯开源的 Puerts 后,作为一名目前主攻游戏客户端开发的菜鸟,我认为有必要进行 TypeScript 的学习(并且自己大一时浅学过 JavaScript,但一直没怎么用上,学了一些就没下文了……),或许还能有助于我在闲暇时学一些前端的东西来满足个人兴趣爱好(?)。
TypeScript 同 JavaScript 的不同
TypeScript 本质上可以说是更“牛逼”的 JavaScript,并且有静态类型检查,在编译体验上(VS Code)个人认为或许会优于 JavaScript。
类型申明
(关于TS中到底有什么数据类型,请自行查阅,这里不做赘述)
示例:
let str:string = "hello world"
let num:number = 10086
let boo:boolean = true
let anything:any = "可以是任意类型" // any 相当于放弃类型检查
// 函数参数和返回值加类型
function calculate(x:number , y:number):number
{
return x + y
}
console.log(calculate(100 , 200)) // 在控制台输出执行结果
常用类型
any
// 以下几行代码不会报错
let a: any
a = 99
a = "typescript"
a = false
// any 类型者可以作为右值
let x = a
console.log(x)
unknown
// 以下几行代码不会报错
let a:unknown
a = 99
a = "typescript"
a = false
// 同 any 类型不同的是,unknown 类型者不能直接被分配作为右值
let x = a // 这里会报错
// 正确做法
let y:string = a as string // 利用断言
y = <string>a // 断言的另一种写法
if (typeof a === 'string') // 利用判断
{
y = a
}
console.log(y)
unknown还有和any的不同之处,例如:
let str1:string = 'hello'
str1.toUpperCase() // 正常
let str2:any = 'hello'
str2.toUpperCase() // 正常
let str3:unknown = 'hello'
str3.toUpperCase() // 报错:str3 的类型为 未知
// 如果不想要报错:
(str3 as string).toUpperCase()
never
// never 者不能正常结束
function demo():never
{
// 返回 never 的函数不能具有可访问的终结点
throw new Error("运行异常!") // 可以用于错误抛出
}
void
void 通常用于函数返回值的声明,含义:函数不返回任何值,调用者也不应该依赖其返回值进行任何操作
function logMessage(msg:string):void
{
console.log(msg)
// 1. 不继续写
// 2. return
// 3. return undefined
}
// 注意:编码者没有编写 return 去指定函数的返回值,所以 logMessage 函数没有显示返回值,但有隐式返回值,即 undefined,虽然函数返回类型为 void,但是也是可以接收undefined。
object
其实有object 和 Object两者之分(没错就是看大小写),二者区别直接看代码:
let a:object // a 能存储的类型必须是 非原始类型
a = new String("123") // new 一个对象出来,这其实不算原始类型
class Person {}
a = new Person()
a = 1 // 这里会报错,因为 number 是原始类型
let b:Object // b 能存储的类型是 可以调用到Object方法的类型
b = new String("bbbb")
b = new Person()
b = 99999
b = "123"
b = false
申明对象类型
let person:{
name:string,
age?:number, // 加上 ? 代表可选属性(可有可无)
isAlive?:boolean,
[key:string]:any // 可自定义添加键值 / 索引签名
}
person = {name:"Max" , age:29 , isAlive:true , gender:"male"}
申明函数类型
let count: (a:number , b:number) => number
count = function(x , y)
{
return x + y
}
申明数组类型
let arr1: string[]
arr1 = ["hello" , "world" , "!"]
let arr2: Array<number>
arr2 = [100 , 200]
tuple
tuple,元组是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组用于精确描述一组的类型,? 表示可选元素。
// tuple 不是关键字
// 申明一个元组
let arr1: [string , number]
let arr2: [string , boolean?]
let arr3: [number , ...string[]]
arr1 = ['hello' , 123]
arr2 = ['world'] // 不写boolean值也是合法的
arr3 = [1 , 'a' , 'b' , ...] // 后面可以有无限多个string
enum
枚举(对于学过unity及C#的同志们来说应该不陌生了)有很多种,可以定义一组命名常量,能增强代码的可读性:
// 数字枚举
enum Direction
{
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
console.log(Direction.Up) // 输出 0
// 字符串枚举
enum Food
{
Banana = "banana",
Apple = "apple",
Bear = "bear"
}
let food: Food = Food.Apple
console.log(food) // 输出 apple
// 常量枚举
// 常量枚举是一种特殊枚举类型,它使用 const 关键字定义,在编译时会被内联,避免生成一些额外的代码
const enum Person
{
Max,
Ham,
Lec,
Rus,
Nor
}
console.log(Person.Rus) // 输出 0
补充:所谓内联,其实就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象,这可以减少生成的 JavaScript 代码量,并提高运行时的性能
type
type可以为任意类型创建变量名,让代码更为简洁,可读性更强,同时能更方便地进行类型复用和扩展。
基本用法
type num = number
let a: num
a = 1
联合类型
type Status = number | string
type Gender = '男' | '女'
// 定义的 Status 可以是 number 也可以是 string
function printStatus(data:Status):void
{
console.log(data)
}
printStatus(404)
printStatus('404')
交叉类型
type Area =
{
height:number
width:number
}
type Address =
{
num:number
cell:number
room:string
}
type House = Area & Address
const house:House =
{
height:100,
width:100,
num:3,
cell:4,
room:'601'
}
特殊情况
type logFunc = () => void
const f1:logFunc = function()
{
return 66 // 这里不会报错,使用类型声明限制函数返回值为void时,TS并不会严格要求函数返回值为空
}
类 / 面向对象相关
构造器
class Person
{
public name:string
age:number // 不添加访问修饰符默认为public
constructor(name:string , age:number)
{
this.name = name
this.age = age
}
// 类方法
speak()
{
console.log('...')
}
}
// 实列化一个类
const p1 = new Person("Name" , 20)
p1.speak()
console.log(p1.name)
继承 及 public访问修饰符
class Student extends Person
{
grade:number
constructor(name:string , age:number , grade:number)
{
super(name , age) // super() - 调用父类的构造器
this.grade = grade
}
study()
{
console.log('Studying...')
}
override speak()
{
console.log("我是学生")
}
}
属性的简写形式
class Person
{
contructor(
public name:string ,
public age:number
) {}
}
protected 及 private 访问修饰符
跟C#、CPP等面向对象编程语言差不多,这里快速过一下语法:
// protected:继承后可以被子类调用;private:继承后不可以被子类调用,只能在类内部进行调用
class Person
{
contructor(
protected name:string,
public age:number,
private identity:string
) {}
protected GetDetails()
{
console.log(this.name + this.age)
}
}
// 不可被改变的类实例化
const p1 = new Person('tom' , 18 , 'aser')
p1.age = 20 // 这里会报错(只读属性)
readonly 修饰符
class Person
{
contructor(
protected name:string,
public readonly age:number, // 如此句,这样age就是只读的属性了
private identity:string
) {}
protected GetDetails()
{
console.log(this.name + this.age)
}
}
抽象类
抽象类不能被实例化,其意义是可以被继承,抽象类里可以有普通方法,也可以有抽象方法。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
abstract class Boss
{
// 构造方法
construct(public HP:number , public ATK:number , public Name:string) {}
// 抽象方法
abstract attack(attackWay:string , attackTarget:string): void
// 具体方法
showup() {}
}
class Boss_1 extends Boss
{
attack(attackWay:string , attackTarget:string):void
{
console.log("Attack!")
}
}
interface 接口类
可以直接参考C#的接口~
interface IPlayer
{
name: string
health: number
Walk(): void
run: (speed: number) => void
}
class Player implements IPlayer
{
constructor(
name: string,
age: number
) {}
Walk(): void
{
console.log("Player is walking.")
}
run(speed)
{
console.log('Player is running at a speed of ${speed}')
}
}
// ts中的接口其实是能作为一个类型去用的
const player: IPlayer = {
name = "Tom"
age = 114514
// 略
}
接口可以继承接口,并且有自动合并,可以重复定义
interface 和 type 的区别
相同点:interface 和 type 都可以用于定义对象结构,两者在许多场景中可以是互换的。
不同点:
- interface 更专注于定义对象和类的结构,支持继承、合并;
- type 可以定义类型别名、联合类型、交叉类型,但不支持继承和合并。
interface 和 抽象类 的区别
相同点:interface 和 抽象类 都用于定义一个类的格式(应该遵循的契约)。
不同点: - interface 只能描述结构,不能有任何实现代码,一个类可以实现多个接口;
- 抽象类 既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类。
泛型
function logData<T , K>(data:T , data_2:K)
{
console.log(data + data_2)
}
logData<string , number>('str' , 100)
logData<number , boolean>(100 , false)
// 泛型之于接口
interface PersonInterface<T>
{
name: string,
extrainfo: T
}
let p: PersonInterface<string> =
{
name: 'Tom',
extrainfo:{
titel: 'man',
address: 'Saga'
}
}
// 泛型之于类
class Person<T>
{
age: number,
extrainfo: T
}
type JobInfo =
{
company: string
time: number
}
const p1 = new Person<JobInfo>(30 , {company:"Tencent" , time: 8})
类型声明文件
假设我们现在手头拿到了一个JS源代码文件(demo.js),需要在TS代码中调用JS文件中的以下两个方法:
JavaScript:
export function add(a,b)
{
return a+b
}
export function mul(a,b)
{
return a*b
}
TypeScript:
import {add , mul} from './demo.js' // 文件路径
console.log(add(1,2))
console.log(mul(3,4))
The End
本人基础的TS语法入门笔记到这里就差不多结束了,后续即将迎来的是Puerts!


