0


接口补充、泛型、工具类型、空安全、模块化

接口补充、泛型、工具类型、空安全、模块化

今日核心:

  1. 接口、泛型、工具类型、空安全、模块化 相关资源:
  2. 图片素材:无

1. 接口补充

接口可以用来定义类型,也有一些其他的用法,咱们来看看

1.1. 接口继承

接口继承使用的关键字是 extends

interface 接口1{
  属性1:类型
}
interface 接口2 extends 接口1{
  属性2:类型
}

试一试:

  1. 定义接口 1
  2. 定义接口 2 继承接口 2
  3. 使用接口 2 进行类型限制

interface IFood {
  name: string
}

interface IVegetable extends IFood {
  color: string
}const flower: IVegetable ={
  name:'西兰花',
  color:'黄绿色'}

1.2. 接口实现

可以通过接口结合 implements 来限制 类 必须要有某些属性和方法,

interface 接口{
  属性:类型
  方法:方法类型
}

class 类 implements 接口{// 必须实现 接口中定义的 属性、方法,否则会报错}

试一试:

  1. 定义接口 a. 定义属性、方法
  2. 定义类 a. 实现接口 b. 额外添加 属性 、方法
  3. 思考: a. 昨天的作业,如何使用接口实现来优化
interface IDog {
  name: string
  bark:()=>void}

class Dog implements IDog {
  name: string =''
  food: string =''bark(){}}

2. 泛型

泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

通俗一点就是:类型是可变的!

目前的学习重点:

  1. 理解泛型的语法,能够看明白系统中使用泛型的位置
  2. 泛型的封装会在项目中逐步增加

为了更好的理解为什么需要 类型可变,咱们来一个具体的例子:一个恒等函数,无论传入什么,直接返回

// 1. 字符串
function identityStr(arg: string): string {return arg
}// 2. 数字
function identityNum(arg: number): number {return arg
}// 3. 布尔
function identityBoolean(arg: boolean): boolean {return arg
}// 如果还要考虑其他类型的参数,那么就需要写很多个类似的函数,逻辑一样,区别就是类型不同// 如果类型可变就好啦~

2.1. 泛型函数

顾名思义就是,泛型和函数结合到一起使用
Type 是泛型参数的名字,类似于之前的形参,
● 可以自行定义,有意义即可
● 首字母大写
● 参见泛型参数名 T、Type

function 函数名<Type>(形参:Type):Type{}// 定义泛型参数 Type,后续可以使用类型的位置均可以使用比如:// 1. 参数类型
function 函数名<Type>(形参:Type){}// 2. 返回值类型
function 函数名<Type>(形参:Type):Type{}

使用泛型改写上一节的代码

// 函数定义
function identity<Type>(arg: Type): Type {return arg;}// 在使用的时候传入具体的类型,函数就可以正常工作啦~
identity<string>('123')
identity<number>(123)
identity<boolean>(false)
identity<string[]>(['1','2','3'])

结合 编译器 的 类型推断 功能,在使用函数的时候还可以进行简写,比如下面的写法,和上面的是一样的。
虽然大部分时候可以推断出类型,但是如果碰到 编译器 无法推断类型时,就需要显式传入类型参数,这在更复杂的示例中可能会发生。

identity('123')identity(123)identity(false)identity(['1','2','3'])

2.2. 使用泛型变量

类型变量可以用在任意支持的位置,实现更为复杂的功能,咱们来看 几 个具体的例子

2.2.1. 返回数组长度

定义函数 :参数是数组,但是数组项的 类型 需要动态设置,数组的长度作为返回值

function getLength<T>(...):number{return...}// 实现之后的代码支持如下调用
getLength<string>(['西兰花','花菜','秋刀鱼'])// 3
getLength<number>([1,2,3])// 3

将类型变量 Type,作为数组项的类型即可

function identity<Type>(arr: Type[]): number {return arr.length
}
2.2.2. 返回数组最后一位元素

一个数组恒等函数,无论传入类型的数组,直接返回数组最后一位

function arrIdentity<Type>(...)...{// ...}// 实现之后的代码支持如下调用
arrIdentity<string>(['西兰花','花菜','秋刀鱼'])// 返回 秋刀鱼
arrIdentity<number>([1,2,3])// 返回原数组 3

参考代码

function arrIdentity<Type>(arr: Type[]): Type {return arr[arr.length-1]}

2.3. 泛型约束

如果开发中不希望任意的类型都可以传递给 类型参数 ,就可以通过泛型约束来完成
核心步骤:

  1. 定义用来约束的 接口(interface)
  2. 类型参数通过 extends 即可实现约束
interface 接口{
  属性:类型
}
function 函数<Type extends 接口>(){}// 后续使用函数时,传入的类型必须要有 接口中的属性

试一试1:
● 实现泛型函数,内部打印 传入 【参数的 length】 属性
● 通过泛型约束,保证传入的参数 【length 属性】
试一试 2:
● 实现泛型函数,内部打印 传入 【参数的 food】 属性
● 通过泛型约束,保证传入的参数 【food 属性】

interface ILengthwise {
  length: number
}

function identity<Type extends ILengthwise>(arr: Type){
  console.log(arr.length.toString())}// 使用的时候 只要有 length 属性即可identity([1,2,3,4])// 数组有 length 属性 正常运行identity('1234')// 字符串也有 length 属性 正常运行// identity(124) // 数值没有 length 属性 报错

class Cloth implements ILengthwise {
  length: number =10}

class Trousers {
  length: number =110}identity(new Cloth())// Cloth 有 length 属性 正常运行identity(new Trousers())// Trousers 有 length 属性 正常运行
interface IFood {
  food: string
}

function logFood<T extends IFood>(param: T){
  console.log('吃的挺好:', param.food)}

class Lunch {
  food: string

  constructor(food: string){
    this.food = food
  }}const l = new Lunch('醉面')logFood(l)

如果要加更多的约束,只需要给作为约束的接口 添加更多的内容即可。

2.4. 多个泛型参数

日常开发的时候,如果有需要可以添加多个 类型变量,只需要定义并使用 多个类型变量即可

function funcA<T, T2>(param1: T, param2: T2){
  console.log('参数 1', param1)
  console.log('参数 2', param2)}

funcA<string, number>('西兰花',998)
funcA<string[], boolean[]>(['西兰花'],[false])

根据需求,可以添加任意个类型变量,添加约束的方式也和之前的章节一致

2.5. 泛型接口

定义接口时结合泛型,那么这个接口就是 泛型接口

interface 接口<Type>{// 内部使用Type}

试一试:

  1. 定义泛型接口,接受泛型参数
  2. 接口定义 a. ids: 数组,类型使用 泛型参数 替代 b. getIds:函数,返回数组,参数类型和返回值类型 使用泛型参数替代
  3. 跳转到【 Array 】的定义
interface IdFunc<Type>{
  id:(value: Type)=> Type
  ids:()=> Type[]}

let obj: IdFunc<number>={id(value){return value },ids(){return[1,3,5]}}

2.6. 泛型类

和泛型接口类似,如果定义类的时候结合泛型,那么这个类就是 泛型类

class 类名<Type>{// 内部可以使用 Type}

试一试

  1. 定义泛型类,接收泛型参数 T
  2. 实现: a. 定义属性,类型为 T b. 构造函数,接收属性,类型为T c. 方法,返回属性,类型为T
// 定义
class Person <T>{
  id: T

  constructor(id: T){
    this.id = id
  }getId(): T {return this.id
  }}// 使用
let p = new Person<number>(10)

3. 工具类型

ArkTS提供了4 中工具类型,来帮组我们简化编码
传送门
4 种工具类型中,熟练掌握 Partial 即可,可以简化编码

3.1. Partial

基于传入的Type类型构造一个【新类型】,将Type的所有属性设置为可选。

type 新类型 = Partial<接口>
type 新类型 = Partial<类>// 后续使用新类型即可

试一试:

  1. 定义 接口,添加属性
  2. 通过 Partial 基于上一步定义的类,返回新类型
  3. 使用 新类型,确认结果
  4. 调整【基础代码】,结合Partial简化默认值的设置
interface Person {
  name: string 
  age: number 
  friends: string[]}

type ParPerson = Partial<Person>// 因为都是可选的,可以设置为空对象
let p: ParPerson ={}

基础模版

interface Food{
  name:string
  color:string
  price:number
  materials:string[]}

@Entry
  @Component
  structPage02_toolType{
    @State message: string ='工具类型';

    food:Food ={}build(){Row(){Column(){Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}}

参考代码:

interface Food{
  name:string
  color:string
  price:number
  materials:string[]}

@Entry
  @Component
  structPage02_toolType{
    @State message: string ='工具类型';// 使用 Partial 将 Food 的每一项转为可选,就不需要都设置默认值啦
    food:Partial<Food>={}build(){Row(){Column(){Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}}

3.2. Required

基于传入的Type类型构造一个【新类型】,将 Type 的所有属性设置为必填

type 新类型 = Required<接口>
type 新类型 = Required<类>// 后续使用新类型即可

试一试:

  1. 定义 类,添加属性,全部设置为可选
  2. 通过 Required 基于上一步定义的类,返回新类型
  3. 使用 新类型,确认是否转为必填属性
class Person {
  name?: string
  age?: number
  friends?: string[]}

type RequiredPerson = Required<Person>// 都是必须得属性,必须设置值
let p: Required<Person>={
  name:'jack',
  age:10,
  friends:[]}

3.3. Readonly

基于 Type构造一个【新类型】,并将Type 的所有属性设置为readonly

type 新类型 = Readonly<接口>
type 新类型 = Readonly<类>// 后续使用新类型即可

试一试:

  1. 定义 类,添加属性
  2. 通过 Readonly 基于上一步定义的类,返回新类型
  3. 使用 新类型,确认结果
class Person {
  name: string =''
  age: number =0}

type ReadonlyIPerson = Readonly<Person>const p: ReadonlyIPerson ={
  name:'jack',
  age:10}

p.name ='rose'// 报错 属性全部变成只读

3.4. Record<Keys,Type>

构造一个对象类型,其属性键为Keys,属性值为Type。该实用程序可用于将一种类型的属性映射到另一种类型。

class CatInfo {
  age: number =0
  breed: string =''}// 联合类型
type CatName ="miffy"|"boris"|"mordred";// 通过Record 构建新的对象类型// 属性名:必须是CatName 中的值// 属性值:必须是CatInfo 类型
type Ctype = Record<CatName, CatInfo>const cats: Ctype ={'miffy':{ age:5, breed:"Maine Coon"},'boris':{ age:5, breed:"Maine Coon"},'mordred':{ age:16, breed:"British Shorthair"},};

试一试:

  1. 运行上述代码尝试通过 cats 获取内部的 CatInfo 对象
  2. 思考:如何添加键的数量?

4. 空安全

默认情况下,ArkTS中的所有类型都是不可为空的。如果要设置为空,需要进行特殊的处理,并且在获取 可能为空的值的时候也需要特殊处理

4.1. 联合类型设置为空

let x: number = null;// 编译时错误
let y: string = null;// 编译时错误
let z: number[]= null;// 编译时错误

通过联合类型指定为空即可,使用时可能需要判断是否为空

// 通过联合类型设置为空
let x: number | null = null;
x =1;// ok
x = null;// ok// 取值的时候,根据需求可能需要考虑 屏蔽掉空值的情况if(x != null){/* do something */}
@Entry
@Component
structIndex{
  @State count: number | null =0build(){Column(){Button('++').onClick(()=>{
          this.count++// 会报错})}}}

4.2. 非空断言运算符

后缀运算符! 可用于断言其操作数为非空。
应用于空值时,运算符将抛出错误。否则,值的类型将从T | null更改为T:

let x: number | null =1;
let y: number
y = x +1;// 编译时错误:无法对可空值作加法
y = x!+1;// 通过非空断言,告诉编译器 x不为 null

4.3. 空值合并运算符

空值合并二元运算符?? 用于检查左侧表达式的求值是否等于null。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b等价于三元运算符a != null ? a : b。
在以下示例中,getNick方法如果设置了昵称,则返回昵称;否则,返回空字符串:

class Person {
  name: string | null = null

  getName(): string {// return this.name === null ? '' : this.name// 等同于 如果 name不为空 就返回 name 反之返回 ''return this.name ??''}}

4.4. 可选链

在访问对象属性时,如果该属性是undefined或者null,可选链运算符会返回undefined。

class Dog {bark(){
    console.log('啊呜~')}}

class Person {
  name?: string
  dog?: Dog

  constructor(name: string, dog?: Dog){
    this.name = name
    this.dog = dog
  }}const p: Person = new Person('jack')// p.dog.bark()// 报错 dog 可能为空// 逻辑判断if(p.dog){
  p.dog.bark()}// 当 dog不为null 或 undefined 时 再调用 bark 并且不会报错
p.dog?.bark()

5. 模块化

接下来咱们开始学习在日常开发中非常重要的一个概念-模块化
概念:

  1. 把一个大的程序拆分成【互相依赖】的若干小文件
  2. 这些小文件还可以通过【特定的语法】组合到一起
  3. 这个过程称之为【模块化】

优点:
a. 更好维护
b. 更好的复用性
缺点:
c. 需要学习模块化语法
分析:

  1. 功能写完只有10行代码,模块化没啥必要!
  2. 功能写完有100行,或者1000行代码,里面有【好几段逻辑】在其他地方也要用–模块化

逻辑都写在一起,并且越写越多------------------模块化之后 (😄)在这里插入图片描述
在这里插入图片描述

5.1. 默认导出和导入

ArkTS 中每个 ets 文件都可以看做是一个模块,默认只能在当前文件中使用,如果要在其他位置中使用就需要:

  1. 当前模块中 导出模块
  2. 需要使用的地方 导入模块
// 默认导出 
export default 需要导出的内容

// 默认导入
import xxx from '模块路径'

试一试:

  1. ets 目录下创建【目录 models】
  2. models 目录下【创建 model1.ets】 a. 定义类 ⅰ. 属性 ⅱ. 构造函数,初始化属性 ⅲ. 方法,内部打印属性 b. 使用【默认导出】语法,将其导出
  3. 页面中【导入 model1.ets 中导出的内容】 并使用

示例代码:

// Model.ets// 一起写
export default class Person {
  name: string =''}// 分开写// export default Person// 只能有一个默认导出// TestModel.ets
import Person from './model'const p: Person = new Person()

5.2. 相对路径

学习了模块拆分之后,就需要开始导入文件啦,这个时候就会用到相对路径,咱们来看看相关的概念
路径:查找文件时,从起点到终点的路线
路径分类:

  1. 相对路径:从当前文件出发去查找目标文件
  2. 绝对路径:从指定盘符触发去查找目标文件
/ 表示进入某个文件夹里面

.  表示当前文件所在文件夹                  ./.. 表示当前文件的上一级文件夹             ../

5.3. 按需导出和导入

如果有很多的东西需要导出,可以使用按需导出,他也有配套的导入语法

// 逐个导出单个特性
export let name1, name2, …, nameN;// also var, const
export let name1 = …, name2 = …, …, nameN;// also var, const
export function FunctionName(){...}
export class ClassName {...}// 一次性导出
export { name1, name2, …, nameN };// --------------------------------// 导入
import { export1, export2, export3 as 别名 } from "module-name";

试一试:

  1. 在 models目录下创建 【model2.ets】
  2. model2.ets中定义 a. 常量 b. 函数 c. 类 d. 接口 e. 使用 【按需导出语法】 导出
  3. 在页面中,导入并使用
// ---------- Model.ets ----------const info: string ='信息'// 单独写
export const num: number =10

function sayHi(){
  console.log('你好吗~')}// 或者 写到 {} 内部
export {
  info, sayHi
}// ---------- MainFile.ets ----------
import { info, num, sayHi as sayHello } from './Model'

console.log('info:', info)// 起别名sayHello()
console.log('num:', num +'')

5.4. 全部导入

导出部分不需要调整,调整导入的语法即可

import * as Utils from './utils'// 通过 Utils 即可获取 utils模块中导出的所有内容

6. 案例-汽车店描述:

接下来通过一个具体的案例来巩固今天学习的语法
需求1:
定义接口 Vehicle(交通工具),记录交通工具的【通用信息】
● 属性:
○ name:字符串类型,名字
○ price:数值类型,价格
○ desc:字符串类型,描述
定义类 Truck(货车),实现 Vehicle 接口
● 属性:
○ load:数值类型,载重
● 构造函数:依次传入属性进行初始化
定义类 Car(轿车),实现 Vehicle 接口
● 属性:
○ color:字符串类型,颜色
○ capacity:数值类型,载客数
定义泛型类 CarShop, 使用 Vehicle 进行泛型约束,泛型参数 T
● 属性
○ name:字符串名字,商店名
○ vehicles: T 数组。使用泛型参数约束的数组
● 构造函数:传入名字进行初始化
● 方法:
○ addVehicle(vehicle:T){}.添加交通工具
■ 参数类型为泛型参数 T,无返回值
■ 传入 Truck 或者 Car 实例,内部添加到 数组 vehicles 中
○ showVehicle(){}.展示交通工具
■ 无参数,无返回值
■ 调用时依次打印 vehicles 数组中的 通用信息
需求 2:
将上述内容 编写到 单独的文件中,暴露 Truck,Car,CarShop。然后测试使用
参考代码:

// 定义接口 Vehicle(交通工具)
interface Vehicle {
  name: string;
  price: number;
  desc: string;}// 定义类 Truck(货车),实现 Vehicle 接口
class Truck implements Vehicle {
  name: string;
  price: number;
  desc: string;
  load: number;constructor(name: string, price: number, desc: string, load: number){
    this.name = name;
    this.price = price;
    this.desc = desc;
    this.load = load;}}// 定义类 Car(轿车),实现 Vehicle 接口
class Car implements Vehicle {
  name: string;
  price: number;
  desc: string;
  color: string;
  capacity: number;constructor(name: string, price: number, desc: string, color: string, capacity: number){
    this.name = name;
    this.price = price;
    this.desc = desc;
    this.color = color;
    this.capacity = capacity;}}// 定义泛型类 CarShop,使用 Vehicle 进行泛型约束,泛型参数 T
class CarShop<T extends Vehicle>{
  name: string;
  vehicles: T[];constructor(name: string){
    this.name = name;
    this.vehicles =[];}// 添加交通工具方法addVehicle(vehicle: T):void{
    this.vehicles.push(vehicle);}// 展示交通工具方法showVehicles():void{for(const vehicle of this.vehicles){
      console.log(`Name: ${vehicle.name}`);
      console.log(`Price: ${vehicle.price}`);
      console.log(`Description: ${vehicle.desc}`);}}}

export {Truck,Car,CarShop}

7. 补充:从TypeScript到ArkTS的适配规

对于有 TS 基础的小伙伴可以参考一下如下的文档,因为并不是所有的 TS 语法都可以在 ArkTS 中使用

标签: java 服务器 前端

本文转载自: https://blog.csdn.net/m0_51766864/article/details/142134274
版权归原作者 一鲸落可万物生 所有, 如有侵权,请联系我们删除。

“接口补充、泛型、工具类型、空安全、模块化”的评论:

还没有评论