面向 Unity 开发者的 TypeScript 快速入门笔记
本文最后更新于9 天前,其中的信息可能已经过时,如有错误请发送邮件到1077511618@qq.com

面向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 都可以用于定义对象结构,两者在许多场景中可以是互换的。
不同点:

  1. interface 更专注于定义对象和类的结构,支持继承、合并
  2. type 可以定义类型别名、联合类型、交叉类型,但不支持继承和合并

    interface 和 抽象类 的区别

    相同点:interface 和 抽象类 都用于定义一个类的格式(应该遵循的契约)。
    不同点:

  3. interface 只能描述结构,不能有任何实现代码,一个类可以实现多个接口;
  4. 抽象类 既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类

泛型

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!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇