接口增补、泛型、工具范例、空安全、模块化
本日焦点:
- 接口、泛型、工具范例、空安全、模块化
相干资源:
- 图片素材:无
1. 接口增补
接口可以用来界说范例,也有一些其他的用法,咱们来看看
1.1. 接口继续
接口继续利用的关键字是 extends- interface 接口1{
- 属性1:类型
- }
- interface 接口2 extends 接口1 {
- 属性2:类型
- }
复制代码 试一试:
- 界说接口 1
- 界说接口 2 继续接口 2
- 利用接口 2 举行范例限定
- interface IFood {
- name: string
- }
- interface IVegetable extends IFood {
- color: string
- }
- const flower: IVegetable = {
- name: '西兰花',
- color: '黄绿色'
- }
复制代码 1.2. 接口实现
可以通过接口联合 implements 来限定 类 必须要有某些属性和方法,- interface 接口{
- 属性:类型
- 方法:方法类型
- }
- class 类 implements 接口{
- // 必须实现 接口中定义的 属性、方法,否则会报错
- }
复制代码 试一试:
- 界说接口
a. 界说属性、方法
- 界说类
a. 实现接口
b. 额外添加 属性 、方法
- 思考:
a. 昨天的作业,怎样利用接口实现来优化
- interface IDog {
- name: string
- bark: () => void
- }
- class Dog implements IDog {
- name: string = ''
- food: string = ''
- bark() {
- }
- }
复制代码 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. 泛型束缚
如果开发中不渴望恣意的范例都可以通报给 范例参数 ,就可以通过泛型束缚来完成
焦点步调:
- 界说用来束缚的 接口(interface)
- 范例参数通过 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
- }
复制代码 试一试:
- 界说泛型接口,担当泛型参数
- 接口界说
a. ids: 数组,范例利用 泛型参数 更换
b. getIds:函数,返回数组,参数范例和返回值范例 利用泛型参数更换
- 跳转到【 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
- }
复制代码 试一试
- 界说泛型类,汲取泛型参数 T
- 实现:
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<类>
- // 后续使用新类型即可
复制代码 试一试:
- 界说 接口,添加属性
- 通过 Partial 基于上一步界说的类,返回新范例
- 利用 新范例,确认效果
- 调解【底子代码】,联合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
- struct Page02_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
- struct Page02_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<类>
- // 后续使用新类型即可
复制代码 试一试:
- 界说 类,添加属性,全部设置为可选
- 通过 Required 基于上一步界说的类,返回新范例
- 利用 新范例,确认是否转为必填属性
- 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<类>
- // 后续使用新类型即可
复制代码 试一试:
- 界说 类,添加属性
- 通过 Readonly 基于上一步界说的类,返回新范例
- 利用 新范例,确认效果
- 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" },
- };
复制代码 试一试:
- 运行上述代码实行通过 cats 获取内部的 CatInfo 对象
- 思考:怎样添加键的数量?
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
- struct Index {
- @State count: number | null = 0
- build() {
- 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. 模块化
接下来咱们开始学习在一样平常开发中非常紧张的一个概念-模块化
概念:
- 把一个大的步调拆分成【相互依靠】的多少小文件
- 这些小文件还可以通过【特定的语法】组合到一起
- 这个过程称之为【模块化】
长处:
a. 更好维护
b. 更好的复用性
缺点:
c. 须要学习模块化语法
分析:
- 功能写完只有10行代码,模块化没啥须要!
- 功能写完有100行,大概1000行代码,内里有【好几段逻辑】在其他地方也要用–模块化
逻辑都写在一起,而且越写越多------------------模块化之后 (😄)
5.1. 默认导出和导入
ArkTS 中每个 ets 文件都可以看做是一个模块,默认只能在当前文件中利用,如果要在其他位置中利用就须要:
- // 默认导出
- export default 需要导出的内容
- // 默认导入
- import xxx from '模块路径'
复制代码 试一试:
- ets 目次下创建【目次 models】
- models 目次下【创建 model1.ets】
a. 界说类
ⅰ. 属性
ⅱ. 构造函数,初始化属性
ⅲ. 方法,内部打印属性
b. 利用【默认导出】语法,将其导出
- 页面中【导入 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. 相对路径
学习了模块拆分之后,就须要开始导入文件啦,这个时间就会用到相对路径,咱们来看看相干的概念
路径:查找文件时,从出发点到尽头的门路
路径分类:
- 相对路径:从当前文件出发去查找目的文件
- 绝对路径:从指定盘符触发去查找目的文件
- / 表示进入某个文件夹里面
- . 表示当前文件所在文件夹 ./
- .. 表示当前文件的上一级文件夹 ../
复制代码 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";
复制代码 试一试:
- 在 models目次下创建 【model2.ets】
- model2.ets中界说
a. 常量
b. 函数
c. 类
d. 接口
e. 利用 【按需导出语法】 导出
- 在页面中,导入并利用
- // ---------- 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 中利用
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|