接口增补、泛型、工具范例、空安全、模块化

[复制链接]
发表于 2025-11-21 18:31:27 | 显示全部楼层 |阅读模式
接口增补、泛型、工具范例、空安全、模块化

本日焦点:

  • 接口、泛型、工具范例、空安全、模块化
    相干资源:
  • 图片素材:无
1. 接口增补

接口可以用来界说范例,也有一些其他的用法,咱们来看看
1.1. 接口继续

接口继续利用的关键字是 extends
  1. interface 接口1{
  2.   属性1:类型
  3. }
  4. interface 接口2 extends 接口1 {
  5.   属性2:类型
  6. }
复制代码
试一试:

  • 界说接口 1
  • 界说接口 2 继续接口 2
  • 利用接口 2 举行范例限定
  1. interface IFood {
  2.   name: string
  3. }
  4. interface IVegetable extends IFood {
  5.   color: string
  6. }
  7. const flower: IVegetable = {
  8.   name: '西兰花',
  9.   color: '黄绿色'
  10. }
复制代码
1.2. 接口实现

可以通过接口联合 implements 来限定 类 必须要有某些属性和方法,
  1. interface 接口{
  2.   属性:类型
  3.   方法:方法类型
  4. }
  5. class 类 implements 接口{
  6.   // 必须实现 接口中定义的 属性、方法,否则会报错
  7. }
复制代码
试一试:

  • 界说接口
    a. 界说属性、方法
  • 界说类
    a. 实现接口
    b. 额外添加 属性 、方法
  • 思考:
    a. 昨天的作业,怎样利用接口实现来优化
  1. interface IDog {
  2.   name: string
  3.   bark: () => void
  4. }
  5. class Dog implements IDog {
  6.   name: string = ''
  7.   food: string = ''
  8.   bark() {
  9.   }
  10. }
复制代码
2. 泛型

泛型在包管范例安全(不丢失范例信息)的同时,可以让函数等与多种差异的范例一起工作,机动可复用
普通一点就是:范例是可变的!
如今的学习重点:

  • 明白泛型的语法,可以或许看明白体系中利用泛型的位置
  • 泛型的封装会在项目中徐徐增长
为了更好的明白为什么须要 范例可变,咱们来一个具体的例子:一个恒等函数,无论传入什么,直接返回
  1. // 1. 字符串
  2. function identityStr(arg: string): string {
  3.   return arg
  4. }
  5. // 2. 数字
  6. function identityNum(arg: number): number {
  7.   return arg
  8. }
  9. // 3. 布尔
  10. function identityBoolean(arg: boolean): boolean {
  11.   return arg
  12. }
  13. // 如果还要考虑其他类型的参数,那么就需要写很多个类似的函数,逻辑一样,区别就是类型不同
  14. // 如果类型可变就好啦~
复制代码
2.1. 泛型函数

顾名思义就是,泛型和函数联合到一起利用
Type 是泛型参数的名字,雷同于之前的形参,
● 可以自行界说,故意义即可
● 首字母大写
● 拜见泛型参数名 T、Type
  1. function 函数名<Type>(形参:Type):Type{
  2. }
  3. // 定义泛型参数 Type,后续可以使用类型的位置均可以使用比如:
  4. // 1. 参数类型
  5. function 函数名<Type>(形参:Type){
  6. }
  7. // 2. 返回值类型
  8. function 函数名<Type>(形参:Type):Type{
  9. }
复制代码
利用泛型改写上一节的代码
  1. // 函数定义
  2. function identity<Type>(arg: Type): Type {
  3.   return arg;
  4. }
  5. // 在使用的时候传入具体的类型,函数就可以正常工作啦~
  6. identity<string>('123')
  7. identity<number>(123)
  8. identity<boolean>(false)
  9. identity<string[]>(['1', '2', '3'])
复制代码
联合 编译器 的 范例推断 功能,在利用函数的时间还可以举行简写,比如下面的写法,和上面的是一样的。
固然大部分时间可以推断出范例,但是如果遇到 编译器 无法推断范例时,就须要显式传入范例参数,这在更复杂的示例中大概会发生。
  1. identity('123')
  2. identity(123)
  3. identity(false)
  4. identity(['1', '2', '3'])
复制代码
2.2. 利用泛型变量

范例变量可以用在恣意支持的位置,实现更为复杂的功能,咱们来看 几 个具体的例子
2.2.1. 返回数组长度

界说函数 :参数是数组,但是数组项的 范例 须要动态设置,数组的长度作为返回值
  1. function getLength<T>(...):number{
  2.   return ...
  3. }
  4. // 实现之后的代码支持如下调用
  5. getLength<string>(['西兰花','花菜','秋刀鱼']) // 3
  6. getLength<number>([1,2,3]) // 3
复制代码
将范例变量 Type,作为数组项的范例即可
  1. function identity<Type>(arr: Type[]): number {
  2.   return arr.length
  3. }
复制代码
2.2.2. 返回数组末了一位元素

一个数组恒等函数,无论传入范例的数组,直接返回数组末了一位
  1. function arrIdentity<Type>(...)... {
  2.    // ...
  3. }
  4. // 实现之后的代码支持如下调用
  5. arrIdentity<string>(['西兰花','花菜','秋刀鱼']) // 返回 秋刀鱼
  6. arrIdentity<number>([1,2,3]) // 返回原数组 3
复制代码
参考代码
  1. function arrIdentity<Type>(arr: Type[]): Type {
  2.   return arr[arr.length-1]
  3. }
复制代码
2.3. 泛型束缚

如果开发中不渴望恣意的范例都可以通报给 范例参数 ,就可以通过泛型束缚来完成
焦点步调:

  • 界说用来束缚的 接口(interface)
  • 范例参数通过 extends 即可实现束缚
  1. interface 接口{
  2.   属性:类型
  3. }
  4. function 函数<Type extends 接口>(){}
  5. // 后续使用函数时,传入的类型必须要有 接口中的属性
复制代码
试一试1:
● 实现泛型函数,内部打印 传入 【参数的 length】 属性
● 通过泛型束缚,包管传入的参数 【length 属性】
试一试 2:
● 实现泛型函数,内部打印 传入 【参数的 food】 属性
● 通过泛型束缚,包管传入的参数 【food 属性】
  1. interface ILengthwise {
  2.   length: number
  3. }
  4. function identity<Type extends ILengthwise>(arr: Type) {
  5.   console.log(arr.length.toString())
  6. }
  7. // 使用的时候 只要有 length 属性即可
  8. identity([1, 2, 3, 4]) // 数组有 length 属性 正常运行
  9. identity('1234') // 字符串也有 length 属性 正常运行
  10. // identity(124) // 数值没有 length 属性 报错
  11. class Cloth implements ILengthwise {
  12.   length: number = 10
  13. }
  14. class Trousers {
  15.   length: number = 110
  16. }
  17. identity(new Cloth()) // Cloth 有 length 属性 正常运行
  18. identity(new Trousers()) // Trousers 有 length 属性 正常运行
复制代码
  1. interface IFood {
  2.   food: string
  3. }
  4. function logFood<T extends IFood>(param: T) {
  5.   console.log('吃的挺好:', param.food)
  6. }
  7. class Lunch {
  8.   food: string
  9.   constructor(food: string) {
  10.     this.food = food
  11.   }
  12. }
  13. const l = new Lunch('醉面')
  14. logFood(l)
复制代码
如果要加更多的束缚,只须要给作为束缚的接口 添加更多的内容即可。
2.4. 多个泛型参数

一样平常开发的时间,如果有须要可以添加多个 范例变量,只须要界说并利用 多个范例变量即可
  1. function funcA<T, T2>(param1: T, param2: T2) {
  2.   console.log('参数 1', param1)
  3.   console.log('参数 2', param2)
  4. }
  5. funcA<string, number>('西兰花', 998)
  6. funcA<string[], boolean[]>(['西兰花'], [false])
复制代码
根据需求,可以添加恣意个范例变量,添加束缚的方式也和之前的章节划一
2.5. 泛型接口

界说接口时联合泛型,那么这个接口就是 泛型接口
  1. interface 接口<Type>{
  2.   // 内部使用Type
  3. }
复制代码
试一试:

  • 界说泛型接口,担当泛型参数
  • 接口界说
    a. ids: 数组,范例利用 泛型参数 更换
    b. getIds:函数,返回数组,参数范例和返回值范例 利用泛型参数更换
  • 跳转到【 Array 】的界说
  1. interface IdFunc<Type> {
  2.   id: (value: Type) => Type
  3.   ids: () => Type[]
  4. }
  5. let obj: IdFunc<number> = {
  6.   id(value) { return value },
  7.   ids() { return [1, 3, 5] }
  8. }
复制代码
2.6. 泛型类

和泛型接口雷同,如果界说类的时间联合泛型,那么这个类就是 泛型类
  1. class 类名<Type>{
  2.   // 内部可以使用 Type
  3. }
复制代码
试一试

  • 界说泛型类,汲取泛型参数  T
  • 实现:
    a. 界说属性,范例为 T
    b. 构造函数,汲取属性,范例为T
    c. 方法,返回属性,范例为T
  1. // 定义
  2. class Person <T> {
  3.   id: T
  4.   constructor(id: T) {
  5.     this.id = id
  6.   }
  7.   getId(): T {
  8.     return this.id
  9.   }
  10. }
  11. // 使用
  12. let p = new Person<number>(10)
复制代码
3. 工具范例

ArkTS提供了4 中工具范例,来帮组我们简化编码
传送门
4 种工具范例中,熟练把握 Partial 即可,可以简化编码
3.1. Partial

基于传入的Type范例构造一个【新范例】,将Type的全下属性设置为可选。
  1. type 新类型 = Partial<接口>
  2. type 新类型 = Partial<类>
  3. // 后续使用新类型即可
复制代码
试一试:

  • 界说 接口,添加属性
  • 通过 Partial 基于上一步界说的类,返回新范例
  • 利用 新范例,确认效果
  • 调解【底子代码】,联合Partial简化默认值的设置
  1. interface Person {
  2.   name: string
  3.   age: number
  4.   friends: string[]
  5. }
  6. type ParPerson = Partial<Person>
  7. // 因为都是可选的,可以设置为空对象
  8. let p: ParPerson = {}
复制代码
底子模版
  1. interface Food{
  2.   name:string
  3.   color:string
  4.   price:number
  5.   materials:string[]
  6. }
  7. @Entry
  8.   @Component
  9.   struct Page02_toolType {
  10.     @State message: string = '工具类型';
  11.     food:Food = {
  12.     }
  13.     build() {
  14.       Row() {
  15.         Column() {
  16.           Text(this.message)
  17.             .fontSize(50)
  18.             .fontWeight(FontWeight.Bold)
  19.         }
  20.         .width('100%')
  21.       }
  22.       .height('100%')
  23.     }
  24.   }
复制代码
参考代码:
  1. interface Food{
  2.   name:string
  3.   color:string
  4.   price:number
  5.   materials:string[]
  6. }
  7. @Entry
  8.   @Component
  9.   struct Page02_toolType {
  10.     @State message: string = '工具类型';
  11.     // 使用 Partial 将 Food 的每一项转为可选,就不需要都设置默认值啦
  12.     food:Partial<Food> = {
  13.     }
  14.     build() {
  15.       Row() {
  16.         Column() {
  17.           Text(this.message)
  18.             .fontSize(50)
  19.             .fontWeight(FontWeight.Bold)
  20.         }
  21.         .width('100%')
  22.       }
  23.       .height('100%')
  24.     }
  25.   }
复制代码
3.2. Required

基于传入的Type范例构造一个【新范例】,将 Type 的全下属性设置为必填
  1. type 新类型 = Required<接口>
  2. type 新类型 = Required<类>
  3. // 后续使用新类型即可
复制代码
试一试:

  • 界说 类,添加属性,全部设置为可选
  • 通过 Required 基于上一步界说的类,返回新范例
  • 利用 新范例,确认是否转为必填属性
  1. class Person {
  2.   name?: string
  3.   age?: number
  4.   friends?: string[]
  5. }
  6. type RequiredPerson = Required<Person>
  7. // 都是必须得属性,必须设置值
  8. let p: Required<Person> = {
  9.   name: 'jack',
  10.   age: 10,
  11.   friends: []
  12. }
复制代码
3.3. Readonly

基于 Type构造一个【新范例】,并将Type 的全下属性设置为readonly
  1. type 新类型 = Readonly<接口>
  2. type 新类型 = Readonly<类>
  3. // 后续使用新类型即可
复制代码
试一试:

  • 界说 类,添加属性
  • 通过 Readonly 基于上一步界说的类,返回新范例
  • 利用 新范例,确认效果
  1. class Person {
  2.   name: string = ''
  3.   age: number = 0
  4. }
  5. type ReadonlyIPerson = Readonly<Person>
  6. const p: ReadonlyIPerson = {
  7.   name: 'jack',
  8.   age: 10
  9. }
  10. p.name = 'rose' // 报错 属性全部变成只读
复制代码
3.4. Record<Keys,Type>

构造一个对象范例,其属性键为Keys,属性值为Type。该实用步调可用于将一种范例的属性映射到另一种范例。
  1. class CatInfo {
  2.   age: number = 0
  3.   breed: string = ''
  4. }
  5. // 联合类型
  6. type CatName = "miffy" | "boris" | "mordred";
  7. // 通过Record 构建新的对象类型
  8. // 属性名:必须是CatName 中的值
  9. // 属性值:必须是CatInfo 类型
  10. type Ctype = Record<CatName, CatInfo>
  11. const cats: Ctype = {
  12.   'miffy': { age: 5, breed: "Maine Coon" },
  13.   'boris': { age: 5, breed: "Maine Coon" },
  14.   'mordred': { age: 16, breed: "British Shorthair" },
  15. };
复制代码
试一试:

  • 运行上述代码实行通过 cats 获取内部的 CatInfo 对象
  • 思考:怎样添加键的数量?
4. 空安全

默认环境下,ArkTS中的全部范例都是不可为空的。如果要设置为空,须要举行特殊的处理处罚,而且在获取 大概为空的值的时间也须要特殊处理处罚
4.1. 团结范例设置为空
  1. let x: number = null;    // 编译时错误
  2. let y: string = null;    // 编译时错误
  3. let z: number[] = null;  // 编译时错误
复制代码
通过团结范例指定为空即可,利用时大概须要判定是否为空
  1. // 通过联合类型设置为空
  2. let x: number | null = null;
  3. x = 1;    // ok
  4. x = null; // ok
  5. // 取值的时候,根据需求可能需要考虑 屏蔽掉空值的情况
  6. if (x != null) { /* do something */ }
复制代码
  1. @Entry
  2. @Component
  3. struct Index {
  4.   @State count: number | null = 0
  5.   build() {
  6.     Column() {
  7.       Button('++')
  8.         .onClick(() => {
  9.           this.count++  // 会报错
  10.         })
  11.     }
  12.   }
  13. }
复制代码
4.2. 非空断言运算符

后缀运算符! 可用于断言其利用数为非空。
应用于空值时,运算符将抛堕落误。否则,值的范例将从T | null更改为T:
  1. let x: number | null = 1;
  2. let y: number
  3. y = x + 1;  // 编译时错误:无法对可空值作加法
  4. y = x! + 1; // 通过非空断言,告诉编译器 x不为 null
复制代码
4.3. 空值归并运算符

空值归并二元运算符?? 用于查抄左侧表达式的求值是否便是null。如果是,则表达式的效果为右侧表达式;否则,效果为左侧表达式。
换句话说,a ?? b等价于三元运算符a != null ? a : b。
在以下示例中,getNick方法如果设置了昵称,则返回昵称;否则,返回空字符串:
  1. class Person {
  2.   name: string | null = null
  3.   getName(): string {
  4.     // return this.name === null ? '' : this.name
  5.     // 等同于 如果 name不为空 就返回 name 反之返回 ''
  6.     return this.name ?? ''
  7.   }
  8. }
复制代码
4.4. 可选链

在访问对象属性时,如果该属性是undefined大概null,可选链运算符会返回undefined。
  1. class Dog {
  2.   bark() {
  3.     console.log('啊呜~')
  4.   }
  5. }
  6. class Person {
  7.   name?: string
  8.   dog?: Dog
  9.   constructor(name: string, dog?: Dog) {
  10.     this.name = name
  11.     this.dog = dog
  12.   }
  13. }
  14. const p: Person = new Person('jack')
  15. // p.dog.bark()// 报错 dog 可能为空
  16. // 逻辑判断
  17. if (p.dog) {
  18.   p.dog.bark()
  19. }
  20. // 当 dog不为null 或 undefined 时 再调用 bark 并且不会报错
  21. p.dog?.bark()
复制代码
5. 模块化

接下来咱们开始学习在一样平常开发中非常紧张的一个概念-模块化
概念:

  • 把一个大的步调拆分成【相互依靠】的多少小文件
  • 这些小文件还可以通过【特定的语法】组合到一起
  • 这个过程称之为【模块化】
长处:
a. 更好维护
b. 更好的复用性
缺点:
c. 须要学习模块化语法
分析:

  • 功能写完只有10行代码,模块化没啥须要!
  • 功能写完有100行,大概1000行代码,内里有【好几段逻辑】在其他地方也要用–模块化
逻辑都写在一起,而且越写越多------------------模块化之后 (😄)


5.1. 默认导出和导入

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

  • 当前模块中 导出模块
  • 须要利用的地方 导入模块
  1. // 默认导出
  2. export default 需要导出的内容
  3. // 默认导入
  4. import xxx from '模块路径'
复制代码
试一试:

  • ets 目次下创建【目次 models】
  • models 目次下【创建 model1.ets】
    a. 界说类
    ⅰ. 属性
    ⅱ. 构造函数,初始化属性
    ⅲ. 方法,内部打印属性
    b. 利用【默认导出】语法,将其导出
  • 页面中【导入 model1.ets 中导出的内容】 并利用
示例代码:
  1. // Model.ets
  2. // 一起写
  3. export default class Person {
  4.   name: string = ''
  5. }
  6. // 分开写
  7. // export default Person
  8. // 只能有一个默认导出
  9. // TestModel.ets
  10. import Person from './model'
  11. const p: Person = new Person()
复制代码
5.2. 相对路径

学习了模块拆分之后,就须要开始导入文件啦,这个时间就会用到相对路径,咱们来看看相干的概念
路径:查找文件时,从出发点到尽头的门路
路径分类:

  • 相对路径:从当前文件出发去查找目的文件
  • 绝对路径:从指定盘符触发去查找目的文件
  1. / 表示进入某个文件夹里面
  2. .  表示当前文件所在文件夹                  ./
  3. .. 表示当前文件的上一级文件夹             ../
复制代码
5.3. 按需导出和导入

如果有很多的东西须要导出,可以利用按需导出,他也有配套的导入语法
  1. // 逐个导出单个特性
  2. export let name1, name2, …, nameN; // also var, const
  3. export let name1 = …, name2 = …, …, nameN; // also var, const
  4. export function FunctionName(){...}
  5. export class ClassName {...}
  6. // 一次性导出
  7. export { name1, name2, …, nameN };
  8. // --------------------------------
  9. // 导入
  10. import { export1, export2, export3 as 别名 } from "module-name";
复制代码
试一试:

  • 在 models目次下创建 【model2.ets】
  • model2.ets中界说
    a. 常量
    b. 函数
    c. 类
    d. 接口
    e. 利用 【按需导出语法】 导出
  • 在页面中,导入并利用
  1. // ---------- Model.ets ----------
  2. const info: string = '信息'
  3. // 单独写
  4. export const num: number = 10
  5. function sayHi() {
  6.   console.log('你好吗~')
  7. }
  8. // 或者 写到 {} 内部
  9. export {
  10.   info, sayHi
  11. }
  12. // ---------- MainFile.ets ----------
  13. import { info, num, sayHi as sayHello } from './Model'
  14. console.log('info:', info)
  15. // 起别名
  16. sayHello()
  17. console.log('num:', num + '')
复制代码
5.4. 全部导入

导出部分不须要调解,调解导入的语法即可
  1. import * as Utils from './utils'
  2. // 通过 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。然后测试利用
参考代码:
  1. // 定义接口 Vehicle(交通工具)
  2. interface Vehicle {
  3.   name: string;
  4.   price: number;
  5.   desc: string;
  6. }
  7. // 定义类 Truck(货车),实现 Vehicle 接口
  8. class Truck implements Vehicle {
  9.   name: string;
  10.   price: number;
  11.   desc: string;
  12.   load: number;
  13.   constructor(name: string, price: number, desc: string, load: number) {
  14.     this.name = name;
  15.     this.price = price;
  16.     this.desc = desc;
  17.     this.load = load;
  18.   }
  19. }
  20. // 定义类 Car(轿车),实现 Vehicle 接口
  21. class Car implements Vehicle {
  22.   name: string;
  23.   price: number;
  24.   desc: string;
  25.   color: string;
  26.   capacity: number;
  27.   constructor(name: string, price: number, desc: string, color: string, capacity: number) {
  28.     this.name = name;
  29.     this.price = price;
  30.     this.desc = desc;
  31.     this.color = color;
  32.     this.capacity = capacity;
  33.   }
  34. }
  35. // 定义泛型类 CarShop,使用 Vehicle 进行泛型约束,泛型参数 T
  36. class CarShop<T extends Vehicle> {
  37.   name: string;
  38.   vehicles: T[];
  39.   constructor(name: string) {
  40.     this.name = name;
  41.     this.vehicles = [];
  42.   }
  43.   // 添加交通工具方法
  44.   addVehicle(vehicle: T): void {
  45.     this.vehicles.push(vehicle);
  46.   }
  47.   // 展示交通工具方法
  48.   showVehicles(): void {
  49.     for (const vehicle of this.vehicles) {
  50.       console.log(`Name: ${vehicle.name}`);
  51.       console.log(`Price: ${vehicle.price}`);
  52.       console.log(`Description: ${vehicle.desc}`);
  53.     }
  54.   }
  55. }
  56. export {Truck,Car,CarShop}
复制代码
7. 增补:从TypeScript到ArkTS的适配规

对于有 TS 底子的小同伴可以参考一下如下的文档,由于并不是全部的 TS 语法都可以在 ArkTS 中利用

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表