鸿蒙应用开发-初见:TS、ArkTS

前言

这篇文章是我在学习HarmonyOS的过程中重新拾遗的知识。之前写RN已经学习过一遍,时间过得久了就有些忘记了,这次写这篇文章也主要是记录一些知识点和之前没注意到的犄角旮旯的知识,也方便自己日后参考。

JS、ES、TS的关系

  1. JS是JavaScript的缩写,ES是ECMAScript的缩写,TS是TypeScript的缩写。
  2. ES是JS的规范,JS是ES规范下的一种实现
  3. TS是JS的一个超集,兼容所有JS特性的同时扩展了静态类型系统,让我们在编写代码的时候更加的安全。

由于TS几乎包含了所有JS的东西,所以我们就直接复习TS就好

TS

基础类型

boolean类型

ts 复制代码
const b: boolean = true;

number类型

ts 复制代码
const num: number = 123;

bigint类型

bigint表示大数类型,它和number类型不兼容

ts 复制代码
let big: bigint =  100n;
let num: number = 6;
big = num;
num = big;

string类型

可以直接使用字面量构建,也可以使用字符串插值语法构建

ts 复制代码
const str: string = 'hello';
const str2: string = `${str} world`;

symbol类型

使用Symbol函数初始化,每一个symbol都是独一无二的

ts 复制代码
const sym: symbol = Symbol();

any类型

  1. any类型类似于OC中的id类型。编译器不会对它进行类型检查
  2. 任何值都可以赋给any类型,any类型的值也可以赋给任何其他类型。即使其他类型已经明确了类型
  3. 我们在使用的时候无特殊情况,坚决不要使用any类型
ts 复制代码
let notSure: any = 666; 
notSure = "semlinker";
notSure = false;

let sureValue: object = { name: 'ohos' };
// 即使sureValue有类型,any类型的notSure也可以赋值给它且不报错
sureValue = notSure;

unknown类型

unknown是作为any类型的替代出现的。任何类型的值都可以赋值给它,但它只能赋值给unknown和any

ts 复制代码
let value:unknow;
value = true;
value = 4;
value = 'hello';
value = [];
value = {};
value = null;
value = undefined;

undefined和null类型

  1. undefined和null是变量未定义以及可选类型时对应的默认值
  2. 默认情况下 nullundefined 是所有类型的子类型。 可以把 nullundefined 赋值给其他类型
ts 复制代码
// null和undefined赋值给string
let str:string = "666";
str = null
str= undefined

// null和undefined赋值给number
let num:number = 666;
num = null
num= undefined

// null和undefined赋值给object
let obj:object ={};
obj = null
obj= undefined

// null和undefined赋值给Symbol
let sym: symbol = Symbol("me"); 
sym = null
sym= undefined

// null和undefined赋值给boolean
let isDone: boolean = false;
isDone = null
isDone= undefined

// null和undefined赋值给bigint
let big: bigint =  100n;
big = null
big= undefined

void类型

void 和 any正好相反,表示没有任何类型。它一般用在函数的返回值类型上

ts 复制代码
function test(): void {
    console.log("just test");
}

never类型

  1. never 类型表示的是那些永不存在的值的类型。
  2. 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
ts 复制代码
// 异常
function error(msg: string): never {
  throw new Error(msg); 
}

// 死循环
function loopForever(): never {
  while (true) {};
}

字面量类型

  1. 字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型
  2. TS 支持3种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型
  3. TypeScript 推断类型时,遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型
ts 复制代码
{ 
    let specifiedStr: 'this is string' = 'this is string'; 
    let str: string = 'any string';
    specifiedStr = str; // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string' 
    str = specifiedStr; // ok
}

特别说明

  1. 上面 specifiedStr的类型就是'this is string',同时它的值也是'this is string'。它并不是一个String类型
  2. 'this is string'可以认为是string的子类型,父类型无法赋值给子类型,子类型可以赋值给父类型。这样你应该就能理解上面倒数两行的代码了

数组类型

有两种写法,一种是类型后加[],一种是使用Array泛型

ts 复制代码
const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];
let arr3: (number | string)[] = [1, "2", 3];
let arr4: {name:string}[] = [{name:"name1"}, {name:"name2"}];

数组解构

可以使用解构语法,快速获取数组中的元素

ts 复制代码
let x: number; let y: number; let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;

数组展开运算符

展开运算符用于快速复制一个数组

ts 复制代码
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];

数组遍历

ts 复制代码
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
  console.log(i);
}

只读数组

ts 复制代码
const arr:readonly number[] = [0, 1];

arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错

Enum类型

普通枚举

  1. 第一个枚举成员初始值默认为0,后一个成员在前一个成员的基础上加1。
  2. 也可以指定枚举成员的初始值,比如下面的PINK初始值为2。后面的成员依然会在上一个成员基础上加1,BLUE的值是3
ts 复制代码
enum Color {
  RED,
  PINK = 2,
  BLUE,
}

字符串枚举

ts 复制代码
enum Color {
  RED = "红色",
  PINK = "粉色",
  BLUE = "蓝色",
}

常量枚举

使用 const 关键字修饰的枚举,常量枚举与普通枚举的区别是,整个枚举会在编译阶段被删除,直接使用常量代替

ts 复制代码
const enum Color {
  RED,
  PINK,
  BLUE,
}
let blue = Color.BLUE;
// 最终转译出的代码类似下面
let blue = 2;

异构枚举的成员值是数字和字符串的混合

ts 复制代码
enum Enum {
  A,
  B,
  C = "C",
  D = "D",
  E = 8,
  F,
}

object, Object 和 {} 类型

object 类型用于表示所有的非原始类型

ts 复制代码
let object: object;
object = 1; // 报错
object = "a"; // 报错
object = true; // 报错
object = null; // 报错
object = undefined; // 报错
object = {}; // 编译正确
object = { foo: 12 };
object = [1, 2, 3];
object = (n: string) => `hello ${n}`;

Object

大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)

ts 复制代码
let bigObject: Object;
object = 1; // 编译正确
object = "a"; // 编译正确
object = true; // 编译正确
object = null; // 报错
ObjectCase = undefined; // 报错
ObjectCase = {}; // ok

{}

{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合

Number、String、Boolean、Symbol

  1. Number、String、Boolean、Symbol 类型,是相应基础类型的包装对象
  2. 这些对象在被基础类型赋值时会进行自动包装
ts 复制代码
let num: number; 
let Num: Number; 
Num = num; // ok 
num = Num; // ts(2322)报错

class 类

ts 复制代码
class Greeter {
   // 只读属性
   readonly id = 'foo';
  // 静态属性
  static cname: string = "Greeter";
  // 成员属性
  greeting: string;

  // 构造函数 - 执行初始化操作
  constructor(message: string) {
    this.greeting = message;
  }

  // 静态方法
  static getClassName() {
    return "Class name is Greeter";
  }

  // 成员方法
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

访问器

在 TypeScript 中,我们可以通过 gettersetter 方法来实现数据的封装和有效性校验,防止出现异常数据

ts 复制代码
let passcode = "Hello TypeScript";

class Employee {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}

let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
  console.log(employee.fullName);
}

对象解构

ts 复制代码
let person = {
  name: "Semlinker",
  gender: "Male",
};

let { name, gender } = person;

对象展开运算符

ts 复制代码
let person = {
  name: "Semlinker",
  gender: "Male",
  address: "Xiamen",
};

// 组装对象
let personWithAge = { ...person, age: 33 };

// 获取除了某些项外的其它项
let { name, ...rest } = person;

可访问修饰符

类的内部成员的外部可访问性,由三个可访问性修饰符(access modifiers)控制:publicprivateprotected

ts 复制代码
class A {
  // `public`修饰符表示这是公开成员,外部可以自由访问
  public greet() {
    console.log("hi!");
  }
  // `private`修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员
  private x:number = 0;
  // `protected`修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用
  protected x = 1;
}

实例属性的简写形式

ts 复制代码
class Point {
  constructor(
    public x:number,
    public y:number
  ) {}
}

元组(tuple)类型

  1. 元组就是元素数量和元素类型确定的数组
  2. 其中元素类型是可以不同的
ts 复制代码
const tuple: [number, string] = [1, "zhangsan"];

元组解构赋值

ts 复制代码
let employee: [number, string] = [1, "Semlinker"]; 
let [id, username] = employee;

元组的可选元素

元组也可以有可选元素,创建元组时可选元素可以不用传

ts 复制代码
let optionalTuple: [string, boolean?]; 
optionalTuple = ["Semlinker", true]; 
optionalTuple = ["Kakuqo"];

元组的剩余元素

元组类型里最后一个元素可以是剩余元素,形式为 ...X,这里 X 是数组类型。剩余元素代表元组类型是开放的,可以有零个或多个额外的元素。

ts 复制代码
type RestTupleType = [number, ...string[]]; 
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];

只读元组类型

ts 复制代码
const point: readonly [number, number] = [10, 20];
const point2: Readonly<[number, number]> = [10, 20];
// Cannot assign to '0' because it is a read-only property.
point[0] = 1;
// Property 'push' does not exist on type 'readonly [number, number]'.
point.push(0);
// Property 'pop' does not exist on type 'readonly [number, number]'.
point.pop();
// Property 'splice' does not exist on type 'readonly [number, number]'.
point.splice(1, 1);

函数

函数类型

TypeScript 提供 Function 类型表示函数,任何函数都属于这个类型

ts 复制代码
function doSomething(f:Function) {
  return f(1, 2, 3);
}

箭头函数

在类中,箭头函数的this是在声明函数时就绑定的

ts 复制代码
const add2 = (x: number, y: number):number => {
    return x + y;
}

函数声明

使用function关键字声明一个函数

ts 复制代码
function add(x: number, y: number): number {
  return x + y;
}

函数表达式

函数也是一种类型,可以使用直接赋值给一个变量

ts 复制代码
const add = function(x: number, y: number): number {
  return x + y;
}

函数和普通变量一样,也有类型

ts 复制代码
let IdGenerator: (chars: string, nums: number) => string;

function createUserId(name: string, id: number): string {
  return name + id;
}

IdGenerator = createUserId;

使用接口定义函数

可以使用接口直接定义函数的类型,接口内直接写函数的参数类型和返回值类型就可以。参数名称可有可无

ts 复制代码
interface Add {
  (x: number, y: number): number;
}

可选参数

如果函数的某个参数可以省略,则在参数名后面加问号表示

可选参数后面不允许再出现必需参数

ts 复制代码
function add(x: number, y?: number): number {
  return y ? x + y : x;
}
const ret = add(10, 10);
const ret2 = add(10);

参数默认值

设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值

ts 复制代码
function add(x: number, y: number = 0): number {
  return x + y;
}

参数解构

ts 复制代码
type ABC = { a:number; b:number; c:number };

function sum({ a, b, c }:ABC) {
  console.log(a + b + c);
}

剩余参数

剩余参数的写法是固定的。参数名称前面三个点。参数类型必须是数组类型

ts 复制代码
function add(...numbers: number[]): number {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

readonly 只读参数

如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上readonly关键字,表示这是只读参数

ts 复制代码
function arraySum(
  arr:readonly number[]
) {
  // ...
  arr[0] = 0; // 报错
}

函数重载

  1. 函数重载是指函数名相同,但参数数量、参数类型、返回结果类型不同的方法
  2. 函数重载真正执行的是同名函数最后定义的函数体 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法 只能定义类型
ts 复制代码
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}

局部类型

函数内部允许声明其他类型,该类型只在函数内部有效,称为局部类型

ts 复制代码
function hello(txt:string) {
  type message = string;
  let newTxt:message = 'hello ' + txt;
  return newTxt;
}

const newTxt:message = hello('world'); // 报错

高阶函数

一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数(higher-order function)

ts 复制代码
(someValue: number) => (multiplier: number) => someValue * multiplier;

类型推论

  1. 如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。
ts 复制代码
let x = 1;
x = true; // 报错
  1. 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
ts 复制代码
let x;
x = 1; // 编译正确
x = true; // 编译正确

类型断言

尖括号写法

ts 复制代码
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;

as 写法

ts 复制代码
let str: any = "to be or not to be";
let strLength: number = (str as string).length;

非空断言

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。

ts 复制代码
let user: string | null | undefined;
console.log(user!.toUpperCase()); // 编译正确
console.log(user.toUpperCase()); // 错误

确定赋值断言

允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值

ts 复制代码
let value!:number
console.log(value); // undefined 编译正确

联合类型

联合类型语法是将多个类型用 | 连接在一块,表示取值可以为多种类型中的一种

ts 复制代码
let status: string | number
status = 'to be or not to be';
status = 1;

可辨识联合

它包含 3 个要点:可辨识、联合类型和类型守卫

这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块

可辨识

可辨识要求联合类型中的每个元素都含有至少一个相同字段的字面量

ts 复制代码
enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

interface Motorcycle {
  vType: "motorcycle"; // discriminant
  make: number; // year
}

interface Car {
  vType: "car"; // discriminant
  transmission: CarTransmission
}

interface Truck {
  vType: "truck"; // discriminant
  capacity: number; // in tons
}

联合类型

基于前面定义的三个接口创建一个 Vehicle 联合类型

ts 复制代码
type Vehicle = Motorcycle | Car | Truck;

类型守卫

我们可以根据联合类型中每个元素都包含的相同字段的值进行辨别,处理不同代码

ts 复制代码
function evaluatePrice(vehicle: Vehicle) {
  switch(vehicle.vType) {
    case "car":
      return vehicle.transmission * EVALUATION_FACTOR;
    case "truck":
      return vehicle.capacity * EVALUATION_FACTOR;
    case "motorcycle":
      return vehicle.make * EVALUATION_FACTOR;
  }
}

交叉类型

交叉类型使用 & 将多个类型连接在一起,交叉类型的实例需要包含所有类型的所有非可选字段

ts 复制代码
interface IpersonA{
  name: string,
  age: number
}
interface IpersonB {
  name: string,
  gender: string
}
let person: IpersonA & IpersonB = { 
    name: "师爷",
    age: 18,
    gender: "男"
};

person 即是 IpersonA 类型,又是 IpersonB 类型,需要同时包含name、age、gender字段

同名基础类型属性的合并

对于同名字段,交叉类型取的多个类型的并集,如果类型不同, 则该key为never类型

ts 复制代码
interface IpersonA {
    name: string
}

interface IpersonB {
    name: number
}

function testAndFn(params: IpersonA & IpersonB) {
    console.log(params)
}

testAndFn({name: "黄老爷"}) // error TS2322: Type 'string' is not assignable to type 'never'.

同名非基础类型属性的合并

ts 复制代码
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: 'semlinker',
    f: 666
  }
};

console.log('abc:', abc);

在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并

类型守卫

类型守卫是运行时检查,确保一个值在所要类型的范围内,目前主要有四种的方式来实现类型保护:

in 关键字

in主要用于判断一个字段是否在对应结构内。不管是否有值

ts 复制代码
interface InObj1 {
    a: number,
    x: string
}
interface InObj2 {
    a: number,
    y: string
}
function isIn(arg: InObj1 | InObj2) {
    // x 在 arg 打印 x
    if ('x' in arg) {
        console.log('x');
    }
    // y 在 arg 打印 y
    if ('y' in arg) {
        console.log('y');
    }
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});

typeof 关键字

  1. typeof主要用于获取基础类型,不适用于类

typeof 只支持:typeof 'x' === 'typeName' 和 typeof 'x' !== 'typeName',x 必须是 'number', 'string', 'boolean', 'symbol'

ts 复制代码
function isTypeof( val: string | number) {
  if (typeof val === "number") {
      return 'number';
  }
  if (typeof val === "string") {
      return 'string';
  }
  return 'typeof 未取出类型';
}

instanceof

instanceof 用于判断一个对象是否是一个类的实例

ts 复制代码
function creatDate(date: Date | string){
    console.log(date)
    if(date instanceof Date){
        date.getDate();
    }else {
        return new Date(date);
    }
}

自定义类型保护的类型谓词

ts 复制代码
function isNumber(num: any): num is number {
    return typeof num === 'number';
}
function isString(str: any): str is string{
    return typeof str=== 'string';
}

接口

ts 复制代码
interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: 'Tom',
    age: 25
};

可选 | 只读属性

  1. 在变量名之前加readonly声明变量是只读的
  2. 在变量名后加问号即可声明变量是可选的
ts 复制代码
interface Person {
  readonly name: string;
  age?: number;
}

索引签名

有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

ts 复制代码
interface Person {
  name: string;
  age?: number;
  [prop: string]: any; //  prop字段必须是 string类型 or number类型。 值是any类型,也就是任意的
}

const p1:Person = { name: "张麻子" };
const p2:Person = { name: "树哥", age: 28 };
const p3:Person = { name: "汤师爷", sex: 1 }

类型别名

类型别名用来给一个类型起个新名字。它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。

ts 复制代码
type count = number | number[];
function hello(value: count) {}

接口与类型别名相同

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。
type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。

接口和类型别名都可以用来描述对象的形状或函数签名

  • 接口
ts 复制代码
interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}
  • 类型别名
ts 复制代码
type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

都允许扩展

  • interface 用 extends 来实现扩展
ts 复制代码
interface MyInterface {
  name: string;
  say(): void;
}

interface MyInterface2 extends MyInterface {
  sex: string;
}

let person:MyInterface2 = {
  name:'树哥',
  sex:'男',
  say(): void {
    console.log("hello 啊,树哥!");
  }
}
  • type 使用 & 实现扩展
ts 复制代码
type MyType = {
  name:string;
  say(): void;
}
type MyType2 = MyType & {
  sex:string;
}
let value: MyType2 = {
  name:'树哥',
  sex:'男',
  say(): void {
    console.log("hello 啊,树哥!");
  }
}

接口与类型别名区别

类型别名可以声明基本数据类型/联合类型/元组等的别名,而接口不行

ts 复制代码
// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

接口能够合并声明,而类型别名不行

ts 复制代码
interface Person {
  name: string
}
interface Person {
  age: number
}
// 此时Person同时具有name和age属性

扩展

接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的

接口 扩展 接口

ts 复制代码
interface PartialPointX { x: number; }
interface Point extends PartialPointX { 
  y: number; 
}

类型别名 扩展 类型别名

ts 复制代码
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

接口 扩展 类型别名

ts 复制代码
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

类型别名 扩展 接口

ts 复制代码
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出

泛型接口

ts 复制代码
interface GenericIdentityFn<T> {
  (arg: T): T;
}

泛型类

ts 复制代码
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

泛型函数

ts 复制代码
function getValue<T>(arg:T):T  {
  return arg;
}

泛型的语法是尖括号 <> 里面写类型参数,一般用 T 来表示,它其实是Type的第一个字母。我们也可以使用任意其他单词表示,只要见名知意即可

使用

我们有两种方式来使用:

  • 定义要使用的类型
ts 复制代码
getValue<string>('树哥'); // 定义 T 为 string 类型
  • 利用 typescript 的类型推断
ts 复制代码
getValue('树哥') // 自动推导类型为 string

多个参数

  1. 其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U
  2. typescript 会自动推断出输入、返回的类型
ts 复制代码
function getValue<T, U>(arg:[T,U]):[T,U] {
  return arg;
}

// 使用
const str = getValue(['树哥', 18]);

泛型约束

使用extends关键字来对泛型类型进行约束,能让我们更有效的使用泛型。

比如下面的Lengthwise约束,不管是 str,arr 还是obj,只要具有 length 属性,都可以

ts 复制代码
interface Lengthwise {
  length: number;
}

function getLength<T extends Lengthwise>(arg:T):T  {
  console.log(arg.length); 
  return arg;
}

使用:

ts 复制代码
const str = getLength('树哥')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })

泛型接口

在定义接口的时候指定泛型

ts 复制代码
interface KeyValue<T,U> {
  key: T;
  value: U;
}

const person1:KeyValue<string,number> = {
  key: '树哥',
  value: 18
}
const person2:KeyValue<number,string> = {
  key: 20,
  value: '张麻子'
}

泛型类

ts 复制代码
class Test<T> {
  value: T;
  add: (x: T, y: T) => T;
}

let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
  return x + y;
};

泛型类型别名

ts 复制代码
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用

ts 复制代码
function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

泛型工具类型

  • typeof

typeof除了做类型保护,还可以在类型上下文中获取变量或者属性的类型

ts 复制代码
//先定义变量,再定义类型
let p1 = {
  name: "树哥",
  age: 18,
  gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
  return p.name;
}
getName(p1);
  • keyof

keyof 用于获取某种类型的所有键,其返回类型是联合类型

ts 复制代码
interface Person {
  name: string;
  age: number;
  gender: "male" | "female";
}

type PersonKey = keyof Person; 
// type PersonKey = 'name'|'age'|'gender';

function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: "树哥", age: 18, gender: "male" }, "name");
console.log(val); // 树哥
  • in

in 用来遍历联合类型。可以配合keyof使用

ts 复制代码
type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }
  • infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用

ts 复制代码
type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;
  • extends

通过 extends 关键字添加泛型约束

ts 复制代码
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
  • [] 索引访问操作符

使用 [] 操作符可以进行索引访问

ts 复制代码
interface Person {
  name: string;
  age: number;
}

type x = Person["name"]; // x is string

装饰器

装饰器是什么

  • 它是一个表达式
  • 该表达式被执行后,返回一个函数
  • 函数的入参分别为 target、name 和 descriptor
  • 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象

装饰器的分类

  • 类装饰器(Class decorators)
  • 属性装饰器(Property decorators)
  • 方法装饰器(Method decorators)
  • 参数装饰器(Parameter decorators)

类装饰器

类装饰器顾名思义,就是用来装饰类的。它的声明如下,接收一个参数:

  • target: TFunction - 被装饰的类
ts 复制代码
declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

我们写一个装饰器来打个招呼

ts 复制代码
function Greeter(greeting: string) {
  return function (target: Function) {
    target.prototype.greet = function (): void {
      console.log(greeting);
    };
  };
}

@Greeter("Hello TS!")
class Greeting {
  constructor() {
    // 内部实现
  }
}

let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello TS!';

属性装饰器

属性装饰器顾名思义,用来装饰类的属性。它的声明如下,接收两个参数:

  • target:(对于实例属性)类的原型对象(prototype),或者(对于静态属性)类的构造函数。
  • propertyKey:所装饰属性的属性名,注意类型有可能是字符串,也有可能是 Symbol 值。
ts 复制代码
type PropertyDecorator =
  (
    target: Object,
    propertyKey: string|symbol
  ) => void;

我们定义一个 logProperty 函数,来跟踪用户对属性的操作,每当设置属性时就会输出一个log

ts 复制代码
function logProperty(target: any, key: string) {
  delete target[key];

  const backingField = "_" + key;

  Object.defineProperty(target, backingField, {
    writable: true,
    enumerable: true,
    configurable: true
  });

  // property getter
  const getter = function (this: any) {
    const currVal = this[backingField];
    console.log(`Get: ${key} => ${currVal}`);
    return currVal;
  };

  // property setter
  const setter = function (this: any, newVal: any) {
    console.log(`Set: ${key} => ${newVal}`);
    this[backingField] = newVal;
  };

  // Create new property with getter and setter
  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person { 
  @logProperty
  public name: string;

  constructor(name : string) { 
    this.name = name;
  }
}

const p1 = new Person("semlinker");
p1.name = "kakuqo";

方法装饰器

方法装饰器顾名思义,用来装饰类的方法。它的声明如下,接收三个参数:

  • target:(对于类的静态方法)类的构造函数,或者(对于类的实例方法)类的原型。
  • propertyKey:所装饰方法的方法名,类型为string|symbol
  • descriptor:所装饰方法的描述对象。
ts 复制代码
type MethodDecorator = <T>(
  target: Object,
  propertyKey: string|symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

我们编写一个log方法装饰器,将方法调用前后的log输出

ts 复制代码
function logger(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log('params: ', ...args);
    const result = original.call(this, ...args);
    console.log('result: ', result);
    return result;
  }
}

class C {
  @logger
  add(x: number, y:number ) {
    return x + y;
  }
}

(new C()).add(1, 2)
// params:  1 2
// result:  3

参数装饰器

参数装饰器顾名思义,是用来装饰函数参数,它的声明如下,接收三个参数:

  • target:(对于静态方法)类的构造函数,或者(对于类的实例方法)类的原型对象。
  • propertyKey:所装饰的方法的名字,类型为string|symbol
  • parameterIndex:当前参数在方法的参数序列的位置(从0开始)。
ts 复制代码
type ParameterDecorator = (
  target: Object,
  propertyKey: string|symbol,
  parameterIndex: number
) => void;

同样的我们写了一个log装饰器,每当函数执行时就会log

ts 复制代码
function log(
  target: Object,
  propertyKey: string|symbol,
  parameterIndex: number
) {
  console.log(`${String(propertyKey)} NO.${parameterIndex} Parameter`);
}

class C {
  member(
    @log x:number,
    @log y:number
  ) {
    console.log(`member Parameters: ${x} ${y}`);
  }
}

const c = new C();
c.member(5, 5);
// member NO.1 Parameter
// member NO.0 Parameter 
// member Parameters: 5 5 

TS开发辅助工具

  1. TS Playground 在线练习TS
  2. TS UML 贴入一段TS代码自动生成UML图
  3. TS AST Viewer贴入一段TS代码,可视化查看这段代码的AST表示

ArkTS

ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集

ArkTS在TS的基础上扩展了struct和很多的装饰器以达到描述UI和状态管理的目的

基本语法

自定义组件必须使用struct定义,并且被Component装饰器修饰

@Component表示这是一个自定义组件

@Entry表示该自定义组件为入口组件

@State表示组件中的状态变量,状态变量变化会触发UI刷新

UI描述:以声明式的方式来描述UI的结构

自定义组件:可复用的UI单元,可组合其他组件

@Builder/@BuilderParam:特殊的封装UI描述的方法,细粒度的封装和复用UI描述

  1. 我们可以在全局或者自定义组件内定义构建函数,使用构建函数可以将更细力度的复用UI
  2. @Builder 装饰的函数想要被接收,接收的参数类型必须被@BuilderParam装饰并且它俩的类型得匹配才可以

比如我们写卡片时都有固定的模板,圆角为8背景色为白色,我们就可以像下面这样封装

ts 复制代码
@Component
struct MyCard {
  @BuilderParam child: () => void

  build() {
    Row() {
      this.child()
    }
    .width("100%")
    .borderRadius(8)
    .backgroundColor(Color.White)
  }
}

然后使用的时候配合上尾随闭包看起来会更自然

ts 复制代码
@Component
struct CustomContainerUser {
  @State text: string = 'header';

  build() {
    Column() {
      // 创建MyCard,在创建MyCard时,通过其后紧跟一个大括号"{}"形成尾随闭包
      // 作为传递给子组件MyCard @BuilderParam child: () => void的参数
      MyCard() {
        Text("我是一个卡片")
      }
    }
  }
}

@Extend/@Styles:扩展内置组件和封装属性样式,更灵活地组合内置组件

@styles

  1. @Styles装饰器可以将多条样式设置提炼成一个方法,定义在组件内容或全局,从而被复用
  2. @Styles仅支持通用属性通用事件且不支持参数
ts 复制代码
// 定义在全局的@Styles封装的样式
@Styles function globalFancy () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }

  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封装的样式
      Text('FancyA')
        .globalFancy ()
        .fontSize(30)
      // 使用组件内的@Styles封装的样式
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

@Extend

@Extend,用于扩展原生组件样式,仅能定义在全局,它支持参数

我们开发过程中经常需要定义字体宏进行使用,在ArkTS中我们可以为Text扩展样式来实现

ts 复制代码
@Extend(Text)
function PFFont(font: string = "PingFang") {
  .fontFamily(font)
}

// PF_S可以调用预定义的PFFont
@Extend(Text)
function PF_S(size: number = 14) {
  .PFFont("PingFang-Sem")
  .fontSize(size)
}

@Extend(Text)
function PF_R(size: number = 14) {
  .PFFont("PingFang-Regular")
  .fontSize(size)
}

stateStyles:多态样式,可以依据组件的内部状态的不同,设置不同样式

状态管理

  1. ArkTS的组件状态管理分为 管理组件拥有的状态管理应用拥有的状态
  2. 组件的状态传递和同步类型分为 只读的单向传递 和 可变更的双向传递。
  • 一般Prop代表的是从父组件到子组件的单向数据流动
  • Link代表的则是父子组件间的双向数据流动

大家直接跳到链接去看吧。里面内容很简单也很容易理解

渲染控制

鸿蒙应用开发-初见:ArkUI🌟🌟🌟

参考资料

  1. Mozila官方JavaScript教程🌟
  2. JavaScript 教程🌟🌟🌟
  3. ES6 教程🌟🌟🌟
  4. TypeScript官网
  5. TypeScript 教程🌟🌟🌟🌟🌟
  6. ArkTS🌟🌟🌟🌟🌟
  7. ArkUI🌟🌟🌟
  8. 鸿蒙开发知识地图🌟🌟🌟🌟
  9. TS 学习指南🌟🌟🌟
  10. juejin.cn/post/721135...
  11. juejin.cn/post/712411...
  12. juejin.cn/post/701880...
  13. juejin.cn/post/706808...
  14. mp.weixin.qq.com/s/N2RPeboN8...
相关推荐
OH五星上将4 小时前
OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(下)
harmonyos·内存管理·openharmony·鸿蒙开发·系统移植·鸿蒙内核·listos_m
云兮Coder4 小时前
鸿蒙Harmony应用开发,数据驾驶舱 项目结构搭建
华为·harmonyos
云兮Coder5 小时前
鸿蒙Harmony应用开发,数据驾驶舱网络请求(Axios) 封装
网络·华为·harmonyos
大众筹码6 小时前
HarmonyOS元服务与卡片
华为·harmonyos
A懿轩A6 小时前
鸿蒙4.0(HarmonyOS 4.0)与鸿蒙Next(HarmonyOS Next)区别
华为·harmonyos·鸿蒙·dev
麦克尔.马7 小时前
一个安卓鸿蒙化工具
android·华为·harmonyos
Android技术栈7 小时前
鸿蒙开发(NEXT/API 12)【跨设备互通特性简介】协同服务
网络·harmonyos·鸿蒙·鸿蒙系统·openharmony·协同·跨设备
青瓷看世界7 小时前
华为HarmonyOS地图服务 6 - 侦听事件来实现地图交互
数码相机·华为·交互·harmonyos
爱桥代码的程序媛9 小时前
鸿蒙OpenHarmony【轻量系统芯片移植】轻量系统STM32F407芯片移植案例
stm32·华为·harmonyos·鸿蒙·鸿蒙系统·移植·openharmony
OH五星上将11 小时前
OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(上)
stm32·嵌入式硬件·harmonyos·openharmony·鸿蒙开发·系统移植