鸿蒙harmony--TypeScript对象详解

生活就是用那一两分的甜,冲淡那八九分的苦,把身体照顾好,把喜欢的事做好,把重要的人待好,你要的一切都在路上!

目录

一,定义

二,属性修饰符

2.1 可选属性

2.2 readonly 属性

三,扩展类型

四,交叉类型

五,泛型对象类型

5.1 Array类型

5.2 ReadonlyArray 类型

5.3 元组类型

5.4 readonly 元组类型

一,定义

在 JavaScript 中,我们分组和传递数据的基本方式是通过对象。在 TypeScript 中,我们通过对象类型来表示它们。

方式一:匿名

function yz(yuanZhen:{name:string,age:number}){
  return yuanZhen.name+yuanZhen.age
}
export default class HanShu{

  test(){
    console.log("s="+yz({name:"袁震",age:30}))
  }

}

方式二:接口

interface YuanZhen{
  name:string
  age:number
}

function yz1(yuanZhen:YuanZhen){
  return yuanZhen.name+yuanZhen.age
}
export default class HanShu{

  test(){
    console.log("s="+yz1({name:"袁震",age:30}))
  }

}

方式三:类型别名

type YuanZhen2={
  name:string,
  age:number
}
function yz2(yuanZhen:YuanZhen2){
  return yuanZhen.name+yuanZhen.age
}
export default class HanShu{

  test(){
    console.log("s="+yz2({name:"袁震",age:30}))
  }

}

在上述所有三个示例中,我们编写的函数接受包含属性 name(必须是 string)和 age(必须是 number)的对象。

二,属性修饰符

对象类型中的每个属性都可以指定一些内容:类型,属性是否可选,属性是否可以写入。

2.1 可选属性

很多时候,我们会发现自己在处理可能具有属性集的对象。在这些情况下,我们可以通过在其名称末尾添加问号 (?) 来将这些属性标记为可选。

interface YuanZhen3{
  name:string
  age?:number
  address?:string
}
function yz3(yuanZhen:YuanZhen3){
  return yuanZhen.name+yuanZhen.age
}

export default class HanShu{

  test(){
    console.log("s="+yz2({name:"袁震",age:30}))
    yz3({name:"袁震"})
    yz3({name:"袁震",age:30})
    yz3({name:"袁震",age:30,address:"淄博"})
  }
}

在这个例子中,age 和 address 都被认为是可选的。我们可以选择提供其中任何一个,因此上面对 yz3 的每个调用都是有效的。所有的可选性真正说明的是,如果设置了属性,它最好有一个特定的类型。

2.2 readonly 属性

对于 TypeScript,属性也可以标记为 readonly。虽然它不会在运行时改变任何行为,但在类型检查期间无法写入标记为 readonly 的属性。

interface YuanZhen4{
  name:string
  readonly age:number
}

function yz4(yuanZhen:YuanZhen4){
  console.info(yuanZhen.name+yuanZhen.age)
  //Cannot assign to 'age' because it is a read-only property. 
  yuanZhen.age=30
}

readonly 标记的属性 只读不可写

使用 readonly 修饰符并不一定意味着值是完全不可变的 - 或者换句话说,其内部内容无法更改。这只是意味着属性本身不能被重写。

interface YuanZhen5{
  readonly a:{name:string,age:number}
}
function yz5(yuanZhen:YuanZhen5){
    //ok
  console.info(yuanZhen.a.name+yuanZhen.a.age)
  yuanZhen.a.name ="袁震"
  yuanZhen.a.age=30
}
function yz55(yuanZhen:YuanZhen5){
  //报错:Cannot assign to 'a' because it is a read-only property. 
  yuanZhen.a ={
    name:"袁震",
    age:30
  }
}

管理对 readonly 含义的期望很重要。在 TypeScript 的开发期间触发关于如何使用对象的意图很有用。TypeScript 在检查两种类型是否兼容时不会考虑这两种类型的属性是否为 readonly,因此 readonly 属性也可以通过别名来更改。

interface Person {
  name: string;
  age: number;
}
 
interface ReadonlyPerson {
  readonly name: string;
  readonly age: number;
}
 
let writablePerson: Person = {
  name: "Person McPersonface",
  age: 42,
};
 
// works
let readonlyPerson: ReadonlyPerson = writablePerson;
 
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'

三,扩展类型

拥有可能是其他类型的更具体版本的类型是很常见的。例如,我们可能有一个 BasicAddress 类型,它描述了在美国发送信件和包所需的字段。

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

在某些情况下这就足够了,但如果某个地址的楼房有多个单元,则地址通常有一个与之关联的单元号。然后我们可以描述一个 AddressWithUnit

interface AddressWithUnit {
  name?: string;
  unit: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

这可以完成工作,但这里的缺点是当我们的更改纯粹是添加时,我们必须重复 BasicAddress 中的所有其他字段。相反,我们可以扩展原来的 BasicAddress 类型,只添加 AddressWithUnit 独有的新字段。

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
  unit: string;
}

interface 上的 extends 关键字允许我们有效地从其他命名类型复制成员,并添加我们想要的任何新成员。这对于减少我们必须编写的类型声明样板的数量以及表明同一属性的几个不同声明可能相关的意图很有用。例如,AddressWithUnit 不需要重复 street 属性,因为 street 源自 BasicAddress,所以读者会知道这两种类型在某种程度上是相关的。

interface 还可以从多种类型扩展:

interface Colorful {
  color: string;
}
 
interface Circle {
  radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

四,交叉类型

interface 允许我们通过扩展其他类型来构建新类型。TypeScript 提供了另一种称为交叉类型的构造,主要用于组合现有的对象类型。

交叉类型是使用 & 运算符定义的。

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
 
type ColorfulCircle = Colorful & Circle;

在这里,我们将 Colorful 和 Circle 相交以生成一个包含 Colorful 和 Circle 的所有成员的新类型。

function draw(circle: Colorful & Circle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}
 
// okay
draw({ color: "blue", radius: 42 });
 
//Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of //type 'Colorful & Circle'.
//  Object literal may only specify known properties, but 'raidus' does not exist in type //'Colorful & Circle'. Did you mean to write 'radius'?
draw({ color: "red", raidus: 42 });

我们只是研究了两种组合相似但实际上略有不同的类型的方法。使用接口,我们可以使用 extends 子句从其他类型扩展,我们可以对交叉做类似的事情,并用类型别名命名结果。两者之间的主要区别在于冲突的处理方式,这种区别通常是你在接口和交叉类型的类型别名之间选择一个而不是另一个的主要原因之一。

五,泛型对象类型

让我们想象一个可以包含任何值的 Box 类型 - stringnumberGiraffe,等等。

interface Box {
  contents: any;
}

现在,contents 属性的类型为 any,虽然有效,但可能会导致事故发生。

我们可以改用 unknown,但这意味着在我们已经知道 contents 的类型的情况下,我们需要进行预防性检查,或者使用容易出错的类型断言。

interface Box {
  contents: unknown;
}
 
let x: Box = {
  contents: "hello world",
};
 
// we could check 'x.contents'
if (typeof x.contents === "string") {
  console.log(x.contents.toLowerCase());
}
 
// or we could use a type assertion
console.log((x.contents as string).toLowerCase());

一种类型安全的方法是为每种类型的 contents 搭建不同的 Box 类型。

interface NumberBox {
  contents: number;
}
 
interface StringBox {
  contents: string;
}
 
interface BooleanBox {
  contents: boolean;
}

但这意味着我们必须创建不同的函数或函数重载,才能对这些类型进行操作。

function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
  box.contents = newContents;
}

这是很多样板。此外,我们稍后可能需要引入新的类型和重载。这令人沮丧,因为我们的盒子类型和重载实际上都是相同的。

相反,我们可以创建一个声明类型参数的泛型 Box 类型。

interface Box {
  contents: Type;
}

你可能会将其理解为“Type 的 Box 是其 contents 具有类型 Type 的东西”。稍后,当我们引用 Box 时,我们必须给出一个类型参数来代替 Type

let box: Box;

将 Box 视为真值类型的模板,其中 Type 是一个占位符,将被其他类型替换。当 TypeScript 看到 Box 时,它会将 Box 中的每个 Type 实例替换为 string,并最终使用 { contents: string } 之类的东西。换言之,Box 和我们之前的 StringBox 工作方式相同。

Box 是可重用的,因为 Type 可以用任何东西代替。这意味着当我们需要一个新类型的盒子时,我们根本不需要声明一个新的 Box 类型(尽管如果我们愿意,我们当然可以)。

interface Box {
  contents: Type;
}
 
interface Apple {
  // ....
}
 
// Same as '{ contents: Apple }'.
type AppleBox = Box;

这也意味着我们可以通过使用 泛型函数 来完全避免重载。

function setContents(box: Box, newContents: Type) {
  box.contents = newContents;
}

值得注意的是,类型别名也可以是泛型的。我们可以定义新的 Box 接口,它是:

interface Box {
  contents: Type;
}

通过使用类型别名来代替:

type Box = {
  contents: Type;
};

由于类型别名与接口不同,它可以描述的不仅仅是对象类型,我们也可以使用它们来编写其他类型的泛型辅助程序类型。

type OrNull = Type | null;
 
type OneOrMany = Type | Type[];
 
type OneOrManyOrNull = OrNull>;
           
type OneOrManyOrNull = OneOrMany | null
 
type OneOrManyOrNullStrings = OneOrManyOrNull;
               
type OneOrManyOrNullStrings = OneOrMany | null

5.1 Array类型

泛型对象类型通常是某种容器类型,它们独立于它们所包含的元素类型工作。数据结构以这种方式工作是理想的,这样它们就可以在不同的数据类型中重用。

我们一直在使用一种类型:Array 型。每当我们写出像 number[] 或 string[] 这样的类型时,这实际上只是 Array 和 Array 的简写。

function doSomething(value: Array) {
  // ...
}
 
let myArray: string[] = ["hello", "world"];
 
// either of these work!
doSomething(myArray);
doSomething(new Array("hello", "world"));

很像上面的 Box 类型,Array 本身是一个泛型类型。

interface Array {
  /**
 
   * Gets or sets the length of the array.
   */
  length: number;
 
  /**
 
   * Removes the last element from an array and returns it.
   */
  pop(): Type | undefined;
 
  /**
 
   * Appends new elements to an array, and returns the new length of the array.
   */
  push(...items: Type[]): number;
 
  // ...
}

5.2 ReadonlyArray 类型

ReadonlyArray 是一种特殊类型,用于描述不应更改的数组。

function doStuff(values: ReadonlyArray) {
  // We can read from 'values'...
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ...but we can't mutate 'values'.
  values.push("hello!");
//Property 'push' does not exist on type 'readonly string[]'.
}

就像属性的 readonly 修饰符一样,它主要是我们可以用于意图的工具。当我们看到一个返回 ReadonlyArray 的函数时,它告诉我们根本不打算更改内容,而当我们看到一个消耗 ReadonlyArray 的函数时,它告诉我们可以将任何数组传递到该函数中,而不必担心它会更改其内容。

与 Array 不同,我们没有可以使用的 ReadonlyArray 构造函数。相反,我们可以将常规 Array 分配给 ReadonlyArray

const roArray: ReadonlyArray = ["red", "green", "blue"];

正如 TypeScript 为 Array 和 Type[] 提供简写语法一样,它也为 ReadonlyArray 和 readonly Type[] 提供简写语法。

function doStuff(values: readonly string[]) {
  // We can read from 'values'...
  const copy = values.slice();
  console.log(`The first value is ${values[0]}`);
 
  // ...but we can't mutate 'values'.
  values.push("hello!");
//Property 'push' does not exist on type 'readonly string[]'.
}

最后要注意的一点是,与 readonly 属性修饰符不同,可赋值性在常规 Array 和 ReadonlyArray 之间不是双向的。

let x: readonly string[] = [];
let y: string[] = [];
 
x = y;
y = x;
//The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

5.3 元组类型

元组类型是另一种 Array 类型,它确切地知道它包含多少个元素,以及它在特定位置包含哪些类型。

type StringNumberPair = [string, number];

这里,StringNumberPair 是 string 和 number 的元组类型。与 ReadonlyArray 一样,它在运行时没有表示,但对 TypeScript 很重要。对于类型系统,StringNumberPair 描述了 0 索引包含 string 和 1 索引包含 number 的数组。

function doSomething(pair: [string, number]) {
  const a = pair[0];
       
//const a: string
  const b = pair[1];
       
//const b: number
  // ...
}
 
doSomething(["hello", 42]);

如果我们试图索引超过元素的数量,我们会得到一个错误。

function doSomething(pair: [string, number]) {
  // ...
 
  const c = pair[2];
//Tuple type '[string, number]' of length '2' has no element at index '2'.
}

我们也可以使用 JavaScript 的数组解构来 解构元组。

function doSomething(stringHash: [string, number]) {
  const [inputString, hash] = stringHash;
 
  console.log(inputString);
                  
//const inputString: string
 
  console.log(hash);
               
//const hash: number
}

元组类型在大量基于约定的 API 中很有用,其中每个元素的含义都是 “明确的”。这使我们在解构变量时可以灵活地命名变量。在上面的示例中,我们可以将元素 0 和 1 命名为我们想要的任何名称。

但是,由于并非每个用户都对显而易见的事物持有相同的看法,因此可能值得重新考虑使用具有描述性属性名称的对象是否更适合你的 API。

除了这些长度检查之外,像这样的简单元组类型相当于声明特定索引属性的 Array 版本的类型,以及使用数字字面量类型声明 length 的类型。

interface StringNumberPair {
  // specialized properties
  length: 2;
  0: string;
  1: number;
 
  // Other 'Array' members...
  slice(start?: number, end?: number): Array;
}

你可能感兴趣的另一件事是元组可以通过写出问号(元素类型后的 ?)来具有可选属性。可选的元组元素只能放在最后,也会影响 length 的类型。

type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
  const [x, y, z] = coord;
              
//const z: number | undefined
 
  console.log(`Provided coordinates had ${coord.length} dimensions`);
                                                  
//(property) length: 2 | 3
}

元组也可以有剩余元素,它们必须是数组/元组类型。

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

StringNumberBooleans 描述了一个元组,其前两个元素分别是 string 和 number,但后面可以有任意数量的 boolean

StringBooleansNumber 描述一个元组,其第一个元素是 string,然后是任意数量的 boolean,最后以 number 结尾。

BooleansStringNumber 描述了一个元组,其起始元素是任意数量的 boolean,并以 string 和 number 结尾。

具有剩余元素的元组没有设置 “length” - 它只有一组不同位置的众所周知的元素。

const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];

为什么可选的和剩余的元素可能有用?好吧,它允许 TypeScript 将元组与参数列表对应起来。元组类型可以在剩余形参和实参中使用,因此如下:

function readButtonInput(...args: [string, number, ...boolean[]]) {
  const [name, version, ...input] = args;
  // ...
}

基本上相当于:

function readButtonInput(name: string, version: number, ...input: boolean[]) {
  // ...
}

当你想用一个剩余参数获取可变数量的参数时,这很方便,并且你需要最少数量的元素,但你不想引入中间变量。

5.4 readonly 元组类型

关于元组类型的最后一点说明 - 元组类型有 readonly 变体,可以通过在它们前面添加 readonly 修饰符来指定 - 就像数组简写语法一样。

function doSomething(pair: readonly [string, number]) {
  // ...
}

正如你所料,TypeScript 中不允许写入 readonly 元组的任何属性。

function doSomething(pair: readonly [string, number]) {
  pair[0] = "hello!";
//Cannot assign to '0' because it is a read-only property.
}

在大多数代码中,元组往往被创建并保持不变,因此尽可能将类型注释为 readonly 元组是一个很好的默认设置。这一点也很重要,因为带有 const 断言的数组字面将使用 readonly 元组类型来推断。

let point = [3, 4] as const;
 
function distanceFromOrigin([x, y]: [number, number]) {
  return Math.sqrt(x ** 2 + y ** 2);
}
 
distanceFromOrigin(point);
//Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.
//  The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.

在这里,distanceFromOrigin 从不修改其元素,但需要一个可变元组。由于 point 的类型被推断为 readonly [3, 4],它不会与 [number, number] 兼容,因为该类型不能保证 point 的元素不会发生修改。

你可能感兴趣的:(typescript,javascript,前端,android,鸿蒙)