HarmonyOS-泛型、工具类型、空安全和模块化

135 阅读5分钟

一、 泛型

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

1. 泛型函数

// 1. 参数类型
function 函数名<Type>(形参:Type){
}

// 2. 返回值类型
function 函数名<Type>(形参:Type):Type{
}

function getData<T>(args:T){
  return [args]
}

interface IDog{   //接口
  name:string
}
class Father{    //类
  name:string
  age:number

  constructor(name:string,age:number) {
    this.name = name
    this.age = age
  }
}
getData<number>(1)
getData<string>('1')
getData<boolean>(false)
getData<number[]>([1,2,3])
getData<IDog>({name:'asd'})  //接口
getData<Father>(new Father('qaq',2)) //类

2. 泛型约束

语法:function 函数<Type extends 约束的类型>(){}

// 联合类型
function Mdy<T extends number | string>(args:T){
  return args
}
Mdy<number>(1)
Mdy<string>('2')
//枚举类型
function Ddd<T extends Color>(args:T){
  return args
}
Ddd<Color>(Color.White)
//接口
interface IPerson{
  name:string
}
function Aaa<T extends IPerson>(args:T){
  return args
}
Aaa<IPerson>({name:'aaa'})
//类
class Dog{
  name:string

  constructor(name:string) {
    this.name = name
  }
}
function Sss<T extends Dog>(args:T){
  return args
}
Sss<Dog>(new Dog('大黄'))

3. 多个泛型参数

语法:function funcA<T, T2>(param1: T, param2: T2) {}

function arr<T1,T2>(a:T1,b:T2){
  console.log(''+a,b)
}
arr<string,boolean>('1',false)
 // 多个泛型约束
function Arr<T1 extends string,T2 extends number>(c:T1,d:T2){
  console.log(''+c,d)
}
Arr<string,number>('2',1)
练一练

使用泛型函数,接受一个参数,根据参数类型 随机生成一个100以内的宽度值。 类型可以是字符串:'90%',也可以是数值:90。

需求:

  • 需要对泛型参数进行类型约定为: string | number
  • 需要在方法体内部判断形参类型判断 typeof 形参
  • 根据形参类型来决定是返回字符串还是返回数字
function Weidth<T extends string|number>(args?:T){//设置为可选参数
  let ran = Math.floor(Math.random()*101) //随机生成0-100,向下取整
  if(typeof args == 'number'){ //判断类型   typeof(判断类型)
    return ran
  }else {
    return `${ran}%` //模板字符串
  }
}

console.log(''+Weidth<number>())
console.log(''+Weidth<string>())

泛型函数练习.png

4. 泛型接口

语法:interface 接口名<Type>{ // 内部使用Type }

interface IData<T>{
  code:number
  msg:string
  data:T
}
 let data1:IData<string[]>={
  code:200,
  msg:'ok'  ,
  data:['1']
} //console.log只能打印字符串 ,对象需要JSON.stringify()才能打印
console.log('',JSON.stringify(data1)) 

5. 泛型类

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

class Person <T> {
  id: T

  constructor(id: T) {
    this.id = id
  }

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

二、 工具类型

1. Partial<Type>

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

type 新类型 = Partial<接口>
type 新类型 = Partial<类>

class Person {
  name: string = ''
  age: number = 0
  friends: string[] = []
}

type ParPerson = Partial<Person>

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

2. Required<Type>

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

type 新类型 = Required<接口>
type 新类型 = Required<类>

3. Readonly<Type>

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

type 新类型 = Readonly<接口>
type 新类型 = Readonly<类>

4. Record<Keys,Type>

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

let person:Record<string,string> = {'name':'张三'}
let name = person['name']    //张三

let person1:Record<string,number> = {'age':20}
let age = person1['age'] //20

三、 空安全

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

1. 联合类型设置为空

// 通过联合类型设置为空
let x: number | null = null
x = 1    // ok
x = null // ok

2. 非空断言运算符

后缀运算符! 可用于断言其操作数为非空。

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

3. 空值合并运算符

空值合并二元运算符?? 用于检查左侧表达式的求值是否等于null。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。

class Person {
  name: string | null = null

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

4. 可选链

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

interface IPerson{
  name:string
  dogAge?:number
}
let person:IPerson = {name:'小明'}
// console.log(person.dogAge.toString()) //报错
console.log(person.dogAge?.toString())  //正确

if(person.dogAge){ // 判断person.dogAge是否为空
  console.log(person.dogAge.toString()) //不为空
}

四、 模块化

概念:

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

逻辑都写在一起,并且越写越多------------------模块化之后 (😄)

模块前.jpg 模块后.jpg

1. 默认导出和导入

// 默认导出 
export default 需要导出的内容

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

试一试:

  1. ets 目录下创建【目录 utils】
  2. models 目录下【创建 tools.ets】文件
    • tools.ets中定义一个 let num:number = 10
    • 使用【默认导出】语法,将其导出
  3. 页面Index.ets中【导入 tools.ets 中导出的内容】 并使用
//目录:src/main/ets/utils/tools.ets
let num:number = 10

// 默认导出num
export default num

//目录:src/main/ets/pages/Index.ets
// 导入模块
import num from '../utils/tools'

// 使用
console.log(num.toString())

2. 相对路径

. 表示当前文件所在文件夹 ./

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

3. 按需导出和导入

导出:

// 按需导出写法1:逐个导出单个特性
export let name1 = '齐天大圣'
export const PI = Math.PI
export function sayHi(){ console.log('我是齐天大圣孙悟空') }
export interface iPerson {
  name:string
}
export class tools {
  static getRandom(num: number) {
    return Math.floor(Math.random() * num)
  }
}
//方法2:一次性导出
let name1 = '齐天大圣'
 const PI = Math.PI
 function sayHi(){ console.log('我是齐天大圣孙悟空') }
 interface iPerson {
  name:string
}
 class tools {
  static getRandom(num: number) {
    return Math.floor(Math.random() * num)
  }
}
export { name1, PI, sayHi,iPerson,tools }

导入:

import {name1, PI, sayHi,iPerson,tools as utils} from '../utils/tools'

4. 全部导入

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

// 一次性全部导入 
import * as utils from '../utils/tools'

let person:utils.iPerson = {name:'孙悟空'}
console.log(utils.name1,utils.PI,person,utils.tools.getRandom(100))
练一练

需求:

  • 请定义一个模块colorManager.ets,导出一个类,里面有一个静态方法,随机生成颜色
  • 在index.ets这个模块中 导入colorManager.ets中的随机颜色生成方法
  • 在.backgroundColor()上使用

导入随机颜色.png

导入随机颜色的成功.png