ArkTs是OpenHarmony主推的应用开发语言,ArkTS围绕应用开发在TypeScript生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,当然这是官方的表述,真正想了解清楚两者的区别当然还是得认真学习一下TypeScript与ArkTS的,完整资料参考typescript超详细入门教程(上)。
1、typescript常用基本数据类型:
类型 | 说明 | 允许的示例 |
number | 数值类型 | 0、0.1、-1、Math.PI、300*10、Infinity、0x2e21 |
string | 字符串类型 | ‘’、‘张三’ |
boolean | 布尔类型 | true\false |
null | 空类型 | null |
undefined | 未定义类型 | undefined |
object | 对象类型,不接受null和undefined | {}、new Map() |
any | 任意类型 | 0、‘张三’、{}、undefined、null、true |
enum | 枚举类型,若未定义值时默认从0开始 | / |
void | 空类型,一般用于函数返回值的类型声明 | let a:() => void,表示a允许被赋值为一个函数,该函数不返回值 |
tuple | 元组,支持接受多种类型的数组,例如[string,number] | ['12',12],支持多样数据类型的组合 |
字面量 | 把指定的值作为类型使用,限制该变量的取值范围 | let type: 'dog'|'cat'|'rabbit' = 'cat',只能从'dog'|'cat'|'rabbit'中取值 |
2、enum枚举类型的使用:
// 定义枚举类型
enum Colors {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
console.log(Colors.Red) // 输出red
3、interface接口的使用:
接口是用于定义对象结构的类型,如果某个字段是对象中非必要的,可以在interface后面增加‘?’表示:
enum Colors {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
console.log(Colors.Red)
interface student {
name: string // 字符串类型
age: number // 数值类型
bool: boolean // 布尔类型
nullEl: null // Null类型
udf: undefined // undefined类型
obj: Object // 对象类型
anyType: any // 任意类型
color: Colors // 枚举类型
result: void // 空类型
numberArr: number[] // 数值类数组
stringArr: string[] // 字符串数组
syb: Symbol // Symbol类型
nessaray?: boolean // 非必选的字段
array: [string, boolean, number] // 元组类型
animals: { name: 'cat' } | 'dog' // 字面量
print?: () => void // 函数
}
const student1: student = {
name: '张三',
age: 0x2e21,
bool: false,
nullEl: null,
udf: undefined,
obj: true,
anyType: null,
color: Colors.Red,
result: (() => console.log(123))(),
numberArr: [1, 2, 3],
stringArr: ['1', '2', '3'],
syb: Symbol('syb'),
print: () => console.log('打印'),
array: ['', true, 23],
animals: { name: 'cat' }
}
student1.print?.()
多余属性检查:
以下这种情况赋值student类型时,会提示多了age属性:
如果要允许多余属性赋值,可以通过类型断言绕开多余属性检查:
第二种方式是通过添加类型索引签名,其中‘key’可以是任意字符串,你定义为prop、a、b这些也无所谓:
4、定义函数
定义函数有两个要点:
1)为函数入参声明类型
2)为函数声明返回类型
interface Cat {
name:string;
}
interface Mouse {
name:string;
}
const tom:Cat = {
name: 'Tom'
}
const jerry:Mouse = {
name: 'Jerry'
}
function tellStory (cat:Cat, mouse:Mouse):void {
console.log(`${cat.name} catch ${mouse.name}`)
}
let tellStory2 = (cat:Cat, mouse:Mouse):void => {
console.log(`${cat.name} catch ${mouse.name}`)
}
tellStory(tom, jerry)// 输出Tom catch Jerry
tellStory2(tom, jerry)// 输出Tom catch Jerry
5、交叉类型
交叉类型使用&符号连接两种类型,可以表示一个返回值具有这两种类型的属性,这里举个例子:警察可以抓贼,医生可以治疗病人,这两个人搭伙可以干什么?
interface Police {
catchTheif:() => void;
}
interface Doctor {
health:() => void;
}
const tom:Police = {
catchTheif: () => {
console.log('我可以抓贼')
}
}
const jerry:Doctor = {
health: () => {
console.log('我可以治疗病人')
}
}
function whatCanYouDo (person1:Police, person2:Doctor):Police & Doctor {
return Object.assign(person1, person2)
}
let partner = whatCanYouDo(tom, jerry)
partner.catchTheif() // 我可以抓贼
partner.health() // 我可以治疗病人
交叉类型中类型A和类型B不能拥有相同的属性,以下情况会报错:
6、联合类型:
如果某个方法适用于类型A和类型B或其他更多类型的参数传入后执行,可以使用联合类型,例如person就可以接受Police和Doctor类型的参数传入,因为他们都具有函数体所需要的属性和方法。
interface Police {
name:string;
run:() => void;
}
interface Doctor {
name:string;
run:() => void;
}
const tom:Police = {
name:'tom',
run: () => {
console.log('我可以跑起来!')
}
}
const jerry:Doctor = {
name:'jerry',
run: () => {
console.log('我也可以跑起来!')
}
}
function whatCanYouDo (person:Police|Doctor):void {
console.log(`${person.name}:`)
person.run()
}
whatCanYouDo(tom)// 输出tom:我可以跑起来!
whatCanYouDo(jerry) // 输出jerry:我也可以跑起来!
如果Doctor缺少name属性时,函数体会提示相关错误:
7、泛型
泛型是指在定义接口、函数、或者类时,不预先定义参数类型,在使用时再指定参数类型的一种特性。
下面这种泛型函数就像是无情的出租车,只要是你给了钱,我就能把你送到目的地,不管乘客是人还是狗:
//<T>预定义泛型类型有哪些类型,T[]代表返回T类型的数组
function fill<T>(param:T):T[] {
return new Array(3).fill(param)
}
console.log(fill(3))
console.log(fill({a: 1}))
但是不是所有的乘客都可以上车,要声明你有钱才行:
interface hasLength {
length:number
}
function getLength<T extends hasLength>(param: T):number {
return param.length
}
const data:hasLength = {
length: 12
}
console.log(getLength('str344')) // 隐藏了参数类型
console.log(getLength<number[]>([12,12,12]))// 展示了参数类型
console.log(getLength(data))// 12
console.log(getLength(12))// 报错
定义泛型函数接口:
interface hasLength {
length:number
}
interface GetLength {
<T extends hasLength>(param:T):number;
}
let getLength:GetLength = (param) => {
return param.length
}
// 泛型定义到外层,供内部使用
interface GetArray<T> {
(arg: T):void;
tag: T;
}
const arr:GetArray<number> = <T>(length: T):void => {
console.log('I has length:', length)
}
arr.tag = 12
interface hasLength2 {
length:number
}
interface hasName {
name:string
}
function logger <T extends (hasLength2 | hasName)>(param:T, prop:string):void {
if(param.hasOwnProperty(prop)) {
console.log('param has property:', prop)
}
}
let value = {name: '张三'}
logger([12],'length')
logger(value, 'we')
8、readonly的使用:
在接口中定义readonly属性,对该属性重新赋值时会提示错误。
定义只读数组:
9、继承
继承单个接口
interface Person {
name: string
}
interface Employee extends Person {
title: string
}
let joe: Employee = {
name: '张三',
title: '前端工程师'
}
console.log(joe)
继承多个接口:
interface Person {
name: string
}
interface Job {
duty: string
}
interface Employee extends Person, Job {
title: string
}
let joe: Employee = {
name: '张三',
title: '前端工程师',
duty: '写代码'
}
console.log(joe)
设计程序的时候有时候会困惑什么时候C extends A,B,什么时候C extends B, B extends A:
往往A和B是不同维度的东西的时候,C要extends A,B
A和B是同一维度的东西或具有从属关系的时候,C extends B, B extends A
比如一个社会心理学的学士(C),他继承了学生(B)的属性,学位是社会心理学,班级是xx年级XX班;同时他也是XX公司的员工,继承了职工(A)的属性,职责是社会调研,工资是3k,这种往往是C要extends A,B
比如狸花猫(C),它是猫科动物(B),同时也是动物(A),这种往往是C extends B, B extends A。
10、修饰符
在ECMAScript中定义类,static是ES标准支持的修饰符:
class Person1 {
MAX_AGE = 150
constructor() {
console.log(this.MAX_AGE) // 通过实例化访问
}
}
new Person1() // 150
class Person {
static MAX_AGE = 150
constructor() {
console.log(Person.MAX_AGE) // 通过类访问
}
}
new Person() // 150
Person.MAX_AGE = 149 // 可以修改该属性
new Person() // 149
Typscript中增加了public、protected、private修饰符,访问权限public > protected > private:
使用private时,意味着该成员是私有的,会控制成员仅能在类内部访问
class Person {
private category: string = 'human'
constructor() {
return this
}
getCategory() {
console.log('getCategory:', this.category)
}
}
let p = new Person()
p.getCategory() // getCategory:human
console.log(p.catagory) // Property 'catagory' is private and only accessible within class 'Person'.ts(2341)
继承的类也无法访问private成员:
class Person {
private category: string = 'human'
constructor() {
return this
}
getCategory() {
console.log('getCategory:', this.category)
}
}
class Student extends Person {
school: string = '春田花花幼儿园'
constructor() {
super()
return this
}
}
let p1 = new Student()
console.log(p1.school)// 春田花花幼儿园
console.log(p1.category)// Property 'category' is private and only accessible within class 'Person'.ts(2341)
p1.getCategory()// getCategory: human
如果该属性是protected的,子类继承时子类内部可以访问父类的protected成员,但是外部或者实例也无法访问:
class Person {
protected category: string = 'human'
constructor() {
return this
}
getCategory() {
console.log('getCategory:', this.category)
}
}
class Student extends Person {
school: string = '春田花花幼儿园'
constructor() {
super()
return this
}
getCategory2() {
console.log('Student访问内部成员:', this.category)
}
}
let p1 = new Student()
p1.getCategory2()//Student访问内部成员: human
console.log(p1.category)//Property 'category' is protected and only accessible within class 'Person' and its subclasses.ts(2445)
11、implements(实现)
类继承类、接口继承接口用extends,类实现接口、类实现抽象类用implements
interface Person {
name: string,
walk: () => void
}
class Joe implements Person {
name: string = 'Joe'
constructor() {
return this
}
walk() {
console.log('I can walk')
}
}
new Joe().walk()// I can walk
12、abstract(抽象)
抽象类一般用于继承,而不能直接使用
abstract class Animal {
name: string = 'Animal'
constructor() {
return this
}
}
console.log(new Animal())// Cannot create an instance of an abstract class.ts(2511)
abstract class Animal {
name: string = 'Animal'
}
class Cat extends Animal {
name: string = 'Cat'
constructor() {
super()
return this
}
say() {
console.log('I am Cat')
}
}
console.log(new Cat().say())// I am Cat
abstract与interface的区别:
- interface用于定义对象的结构,描述对象包含的属性和方法,无法设置值和实现方法;
- abstract用于作为子类的基类,描述基类包含的属性或方法,允许包含通用方法的实现或属性的定义;
- interface可被interface继承,也可以被class实现
- abstract可以被abstract继承,也可以被class继承
exends和implements的区别:
- extends可用于子类继承父类,interface继承interface
- implements用于class对interface的实现
13、装饰器
装饰器的作用类似于房屋建筑的装饰物,在不影响建筑物的情况下增加一些装饰作用,例如灯具、挂饰。
装饰器的优点:
- 代码复用:装饰器允许你把增强功能封装到独立的模块中,从而避免代码重复。
- 解耦合:通过装饰器可以将通用的功能和具体的实现分离,从而使得代码更加灵活和可维护。
- 增强功能:可以通过装饰器来添加额外功能,比如日志记录、权限验证、缓存等。
使用装饰器前需要在tsconfig.json中开启装饰器编译:
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
例如下面这个例子:
// 日志装饰器
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// target指向User
// propertyKey指向装饰器装饰的方法名,这里的值为"updateProfile"
// descriptor指向描述当前装饰器装饰的方法属性描述符
// descriptor.value指向当前装饰器装饰的方法,这里指向updateProfile方法
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// 重新设置装饰器装饰的方法,增加日志代理
console.log(`Method ${propertyKey} was called with args: ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
}
class User {
@Log
updateProfile(name: string, age: number) {
console.log(`Updating profile for ${name}, age ${age}`);
}
}
const user = new User();
// 调用updateProfile方法时会触发Log方法这个钩子,输入代理日志
user.updateProfile("Alice", 30);
14、使用typescript创建俄罗斯方块游戏
demo:Tetris: 俄罗斯方块
类设计说明
俄罗斯方块游戏的类设计分为以下几个类:
1、View:游戏视图的基类,实现了初始化游戏区域数据、清空游戏区域、绘制方块等方法
2、DataView:游戏数据视图,继承了View类,负责绘制已落下的俄罗斯方块,消除一行时通知UI更新得分
3、BlockView:方块视图,叠加在DataView上方的俄罗斯方块数据视图,继承了View类,绘制下落的俄罗斯方块,方块落下时如果无法继续下落,会把方块同步到DataView中,再绘制下一个俄罗斯方块
4、Preview:预览视图,负责绘制下一个俄罗斯方块,告诉玩家下一个俄罗斯方块是什么
5、Interaction:交互类,为UI类提供接口操作BlockView的方块移动、旋转等
6、UI类:负责创建游戏UI视图,调用Interaction的方法操作BlockView的方块移动
7、Block类:俄罗斯方块的基类,提供了方块旋转、随机方法
8、Block1/Block2/Block3等类:描述不同俄罗斯方块的不同数据内容,生成的Block会添加到BlockView中