鸿蒙应用开发-初见:TS

2,049 阅读21分钟

前言

这篇文章是我在学习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

let、const

  1. let是为了解决JavaScript中var声明变量时的种种问题而存在的
  2. let和const都支持块级作用域,都必须在声明后使用,在相同作用域中都不允许重复声明
let a = 10;
const b = true;
const c = {};

const保证的不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量

对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),无法保证它指向的数据结构是不是可变

基础类型

boolean类型

const b: boolean = true;

number类型

const num: number = 123;

bigint类型

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

let big: bigint =  100n;
let num: number = 6;
big = num;
num = big;

string类型

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

const str: string = 'hello';
const str2: string = `${str} world`;

symbol类型

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

const sym: symbol = Symbol();

any类型

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

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

unknown类型

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

let value:unknow;
value = true;
value = 4;
value = 'hello';
value = [];
value = {};
value = null;
value = undefined;

undefined和null类型

  1. undefined和null是变量未定义以及可选类型时对应的默认值
  2. 默认情况下 null 和 undefined 是所有类型的子类型。 可以把 null 和 undefined 赋值给其他类型
// 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正好相反,表示没有任何类型。它一般用在函数的返回值类型上

function test(): void {
    console.log("just test");
}

never类型

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

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

字面量类型

  1. 字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型
  2. TS 支持3种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型
  3. TypeScript 推断类型时,遇到const命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型
{ 
    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泛型

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"}];

数组解构

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

本质上,解构写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

不完全解构

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

解构默认值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

数组展开运算符

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

let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];

数组遍历

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

for...in 与 for...of

for...in循环读取键名,for...of循环读取键值

var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}

只读数组

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
enum Color {
  RED,
  PINK = 2,
  BLUE,
}

字符串枚举

enum Color {
  RED = "红色",
  PINK = "粉色",
  BLUE = "蓝色",
}

常量枚举

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

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

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

enum Enum {
  A,
  B,
  C = "C",
  D = "D",
  E = 8,
  F,
}

object, Object 和 {} 类型

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

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 不可以)

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. 这些对象在被基础类型赋值时会进行自动包装
let num: number; 
let Num: Number; 
Num = num; // ok 
num = Num; // ts(2322)报错

class 类

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 中,我们可以通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据

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);
}

对象解构

  1. let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
  2. 上面一行对象的解构赋值是下面形式的简写
  3. 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
  4. let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// 上面一行对象的解构赋值是下面形式的简写
// 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
// let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

foo // "aaa"
bar // "bbb"

解构到另外一个变量

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

解构指定默认值

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

对象展开运算符

let person = {
  name: "Semlinker",
  gender: "Male",
  address: "Xiamen",
};

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

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

可访问修饰符

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

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

实例属性的简写形式

class Point {
  constructor(
    public x:number,
    public y:number
  ) {}
}

元组(tuple)类型

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

元组解构赋值

let employee: [number, string] = [1, "Semlinker"]; 
let [id, username] = employee;

元组的可选元素

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

let optionalTuple: [string, boolean?]; 
optionalTuple = ["Semlinker", true]; 
optionalTuple = ["Kakuqo"];

元组的剩余元素

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

type RestTupleType = [number, ...string[]]; 
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];

只读元组类型

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 类型表示函数,任何函数都属于这个类型

function doSomething(f:Function) {
  return f(1, 2, 3);
}

箭头函数

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

const add2 = (x: number, y: number):number => {
    return x + y;
}

函数声明

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

function add(x: number, y: number): number {
  return x + y;
}

函数表达式

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

const add = function(x: number, y: number): number {
  return x + y;
}

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

let IdGenerator: (chars: string, nums: number) => string;

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

IdGenerator = createUserId;

使用接口定义函数

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

interface Add {
  (x: number, y: number): number;
}

可选参数

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

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

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

参数默认值

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

function add(x: number, y: number = 0): number {
  return x + y;
}

参数解构

type ABC = { a:number; b:number; c:number };

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

剩余参数

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

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

readonly 只读参数

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

function arraySum(
  arr:readonly number[]
) {
  // ...
  arr[0] = 0; // 报错
}

函数重载

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

局部类型

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

function hello(txt:string) {
  type message = string;
  let newTxt:message = 'hello ' + txt;
  return newTxt;
}

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

高阶函数

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

(someValue: number) => (multiplier: number) => someValue * multiplier;

类型推论

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

类型断言

尖括号写法

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

as 写法

let str: any = "to be or not to be";
let strLength: number = (str as string).length;

非空断言

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

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

确定赋值断言

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

let value!:number
console.log(value); // undefined 编译正确

联合类型

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

let status: string | number
status = 'to be or not to be';
status = 1;

可辨识联合

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

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

可辨识

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

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 联合类型

type Vehicle = Motorcycle | Car | Truck;

类型守卫

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

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;
  }
}

交叉类型

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

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类型

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'.

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

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主要用于判断一个字段是否在对应结构内。不管是否有值

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'

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

instanceof

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

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

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

function isNumber(num: any): num is number {
    return typeof num === 'number';
}
function isString(str: any): str is string{
    return typeof str=== 'string';
}

接口

interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: 'Tom',
    age: 25
};

可选 | 只读属性

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

索引签名

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

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

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 }

类型别名

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

type count = number | number[];
function hello(value: count) {}

接口与类型别名相同之处

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。

type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。

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

  • 接口
interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}

  • 类型别名
type Point = {
  x: number;
  y: number;
};

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

都允许扩展

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

interface MyInterface2 extends MyInterface {
  sex: string;
}

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

接口与类型别名不同之处

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

// primitive
type Name = string;

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

// union
type PartialPoint = PartialPointX | PartialPointY;

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

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

interface Person {
  name: string
}
interface Person {
  age: number
}
// 此时Person同时具有name和age属性

扩展

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

接口 扩展 接口

interface PartialPointX { x: number; }
interface Point extends PartialPointX { 
  y: number; 
}

类型别名 扩展 类型别名

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

接口 扩展 类型别名

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

类型别名 扩展 接口

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

泛型

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

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

泛型接口

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

interface KeyValue<T,U> {
  key: T;
  value: U;
}

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

泛型类

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;
};

泛型类型别名

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

泛型函数

function getValue<T>(arg:T):T  {
  return arg;
}

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

使用

我们有两种方式来使用:

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

多个参数

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

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

泛型约束

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

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

interface Lengthwise {
  length: number;
}

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

使用:

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

泛型参数的默认类型

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

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除了做类型保护,还可以在类型上下文中获取变量或者属性的类型

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

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

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使用

type Keys = "a" | "b" | "c"

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

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

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

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

interface Lengthwise {
  length: number;
}

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

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

interface Person {
  name: string;
  age: number;
}

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

异步

Promise

  1. Promise是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
  2. Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
  3. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
  4. Promise原型对象上有thencatchfinally函数
  • then Promise 实例添加状态改变时的回调函数,方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。它同时也返回一个新的Promis实例
  • catch 指定发生错误时的回调函数,
  • finally 指定不管 Promise 对象最后状态如何,都会执行的操作
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

常用方法

Promise.all()

  1. Promise.all() 将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.all([p1, p2, p3]);

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

Promise.race()

将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.race([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

Promise.allSettled()

Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

Promise.any()

接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {  // 只要有一个 fetch() 请求成功
  console.log(first);
}).catch((error) => { // 所有三个 fetch() 全部请求失败
  console.log(error);
});

Promise.resolve()

将现有对象转为 fullied状态的Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.reject()

将现有对象转为 rejected状态的Promise 对象

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

async 函数

  1. async 是 Generator 函数的语法糖。
  2. async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
  3. async函数返回一个 Promise 对象。函数内部return语句返回的值,会成为then方法回调函数的参数
  4. 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

await 命令

await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

async 函数的错误处理

  1. 使用try catch处理
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
  1. 使用 Promise对象的catch方法
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

async函数的多种声明方式

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

多个async函数顺序执行

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

多个async函数并发执行

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

内置的类型工具

ConstructorParameters<Type>

提取构造方法Type的参数类型,组成一个元组类型返回

type T1 = ConstructorParameters<
  new (x: string, y: number) => object
>; // [x: string, y: number]

type T2 = ConstructorParameters<
  new (x?: string) => object
>; // [x?: string | undefined]

Exclude<UnionType, ExcludedMembers>

从联合类型UnionType里面,删除某些类型ExcludedMembers,组成一个新的类型返回

type T1 = Exclude<'a'|'b'|'c', 'a'>; // 'b'|'c'
type T2 = Exclude<'a'|'b'|'c', 'a'|'b'>; // 'c'
type T3 = Exclude<string|(() => void), Function>; // string
type T4 = Exclude<string | string[], any[]>; // string
type T5 = Exclude<(() => void) | null, Function>; // null
type T6 = Exclude<200 | 400, 200 | 201>; // 400
type T7 = Exclude<number, boolean>; // number

Extract<UnionType, Union>

从联合类型UnionType之中,提取指定类型Union,组成一个新类型返回。它与Exclude<T, U>正好相反

type T1 = Extract<'a'|'b'|'c', 'a'>; // 'a'
type T2 = Extract<'a'|'b'|'c', 'a'|'b'>; // 'a'|'b'
type T3 = Extract<'a'|'b'|'c', 'a'|'d'>; // 'a'
type T4 = Extract<string | string[], any[]>; // string[]
type T5 = Extract<(() => void) | null, Function>; // () => void
type T6 = Extract<200 | 400, 200 | 201>; // 200

InstanceType<Type>

提取构造函数的返回值的类型(即实例类型),参数Type是一个构造函数,等同于构造函数的ReturnType<Type>

type T = InstanceType<
  new () => object
>; // object

NonNullable<Type>

从联合类型Type删除null类型和undefined类型,组成一个新类型返回,也就是返回Type的非空类型版本

// string|number
type T1 = NonNullable<string|number|undefined>;

// string[]
type T2 = NonNullable<string[]|null|undefined>;

type T3 = NonNullable<boolean>; // boolean
type T4 = NonNullable<number|null>; // number
type T5 = NonNullable<string|undefined>; // string
type T6 = NonNullable<null|undefined>; // never

Omit<Type, Keys>

从对象类型Type中,删除指定的属性Keys,组成一个新的对象类型返回

interface A {
  x: number;
  y: number;
}

type T1 = Omit<A, 'x'>;       // { y: number }
type T2 = Omit<A, 'y'>;       // { x: number }
type T3 = Omit<A, 'x' | 'y'>; // { }

OmitThisParameter<Type>

从函数类型中移除 this 参数

function toHex(this: Number) {
  return this.toString(16);
}

type T = OmitThisParameter<typeof toHex>; // () => string

Parameters<Type>

从函数类型Type里面提取参数类型,组成一个元组返回

type T1 = Parameters<() => string>; // []

type T2 = Parameters<(s:string) => void>; // [s:string]

type T3 = Parameters<<T>(arg: T) => T>;    // [arg: unknown]

type T4 = Parameters<
  (x:{ a: number; b: string }) => void
>; // [x: { a: number, b: string }]

type T5 = Parameters<
  (a:number, b:number) => number
>; // [a:number, b:number]

Partial<Type>

返回一个新类型,将参数类型Type的所有属性变为可选属性。

interface A {
  x: number;
  y: number;
}
 
type T = Partial<A>; // { x?: number; y?: number; }

Pick<Type, Keys>

返回一个新的对象类型,第一个参数Type是一个对象类型,第二个参数KeysType里面被选定的键名

interface A {
  x: number;
  y: number;
}

type T1 = Pick<A, 'x'>; // { x: number }
type T2 = Pick<A, 'y'>; // { y: number }
type T3 = Pick<A, 'x'|'y'>;  // { x: number; y: number }

Readonly<Type>

返回一个新类型,将参数类型Type的所有属性变为只读属性。

interface A {
  x: number;
  y?: number;
}

// { readonly x: number; readonly y?: number; }
type T = Readonly<A>;

Record<Keys, Type>

返回一个对象类型,参数Keys用作键名,参数Type用作键值类型

// { a: number }
type T = Record<'a', number>;

Required<Type>

返回一个新类型,将参数类型Type的所有属性变为必选属性。它与Partial<Type>的作用正好相反

interface A {
  x?: number;
  y: number;
}

type T = Required<A>; // { x: number; y: number; }

ReadonlyArray<Type>

生成一个只读数组类型,类型参数Type表示数组成员的类型

const values: ReadonlyArray<string> 
  = ['a', 'b', 'c'];

values[0] = 'x'; // 报错
values.push('x'); // 报错
values.pop(); // 报错
values.splice(1, 1); // 报错

ReturnType<Type>

提取函数类型Type的返回值类型,作为一个新类型返回

type T1 = ReturnType<() => string>; // string

type T2 = ReturnType<() => {
  a: string; b: number
}>; // { a: string; b: number }

type T3 = ReturnType<(s:string) => void>; // void

type T4 = ReturnType<() => () => any[]>; // () => any[]

type T5 = ReturnType<typeof Math.random>; // number

type T6 = ReturnType<typeof Array.isArray>; // boolean

ThisParameterType<Type>

提取函数类型中this参数的类型

function toHex(this: Number) {
  return this.toString(16);
}

type T = ThisParameterType<typeof toHex>; // number

Uppercase<StringType>

将字符串类型的每个字符转为大写

type A = 'hello';

// "HELLO"
type B = Uppercase<A>;

Lowercase<StringType>

将字符串的每个字符转为小写

type A = 'HELLO';

// "hello"
type B = Lowercase<A>;

Capitalize<StringType>

将字符串的第一个字符转为大写

type A = 'hello';

// "Hello"
type B = Capitalize<A>;

Uncapitalize<StringType>

将字符串的第一个字符转为小写

type A = 'HELLO';

// "hELLO"
type B = Uncapitalize<A>;

装饰器

装饰器是什么

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

装饰器的分类

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

类装饰器

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

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

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

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 值。
type PropertyDecorator =
  (
    target: Object,
    propertyKey: string|symbol
  ) => void;

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

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:所装饰方法的描述对象。
type MethodDecorator = <T>(
  target: Object,
  propertyKey: string|symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

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

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开始)。
type ParameterDecorator = (
  target: Object,
  propertyKey: string|symbol,
  parameterIndex: number
) => void;

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

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

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

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

鸿蒙应用开发-窥探:State装饰器

参考资料

  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…