第二节 鸿蒙应用开发语言

概述

鸿蒙应用的主要的开发语言是ArkTS,它是由TypeScript(TS)扩展而来的,而TS本身也是由另外一门语言JavaScript(JS)扩展而来的。

  • JavaScript

    • 特点

      解释型语言、弱类型语言、事件驱动、跨平台性

    • 应用领域

      网页开发、服务器端开发、移动应用开发、桌面应用开发

    • 语言优势

      学习门槛低、非常丰富的第三方库和框架、活跃的社区

  • TypeScript

    • 特点

      强类型语言、面向对象编程、静态类型检查、与JavaScript高度兼容

    • 应用领域

      大型企业级项目开发、前端框架的开发例如:Vue3等

    • 语言优势

      提高开发效率、更好的代码质量、良好的工具支持

  • ArkTS

    • 概念

      ArkTS是一种面向全场景、多设备的声明式编程语言,主要用于华为操作系统的应用开发。

    • 特点

      声明式编程风格、高效的性能、多设备适配、丰富的组件和API

    • 应用领域

      鸿蒙应用开发、跨设备应用开发、物联网应用开发

    • 语言优势

      简洁高效、强大的生态系统、未来发展潜力

另外的开发语言包括Java、C/C++、JavaScript。

TypeScript快速入门

运行环境:线上的Playground(https://www.typescriptlang.org/zh/play)/本地运行环境

本地运行环境安装配置步骤:

  1. 安装VSCode编辑器(Visual Studio Code - Code Editing. Redefined
  2. 在VSCode里面安装Code Runner插件
  3. ts-node(因为依赖于Node环境,所以要先安装Nodejs环境,Nodejs往期版本下载地址:Index of /download/release/latest-v16.x/,然后配置Nodejs的环境变量),打开命令行,输入npm install ts-node

TS语法:

  • 声明

    • 变量声明

      let a: number = 100
      let GG_a1: any = true  // 谨慎使用
      
    • 常量声明

      const a: number = 100
      
  • 类型推断

    let a = 100
    console.log(typeof a)
    

    建议还是要手动注明类型,可以提高代码的可读性和可维护性、保持代码的一致性、让工具有智能提示和自动补全的功能。

  • 常用数据类型

    • number类型(包括整数、浮点数)

      • 数值范围

        默认双精度64位的浮点数,最大/小值Number.MAX_VALUE/Number.MIN_VALUE,如果超出这个范围可能会出现不精确或者特殊值(Infinity和-Infinity)的情况

      • 精度问题

        console.log(0.1 + 0.2)得0.30000000000000004

      • 类型转换

        当从其他类型转换成数字类型的时候,要注意可能出现的意外结果

      • 算术运算和溢出

        在一些比较大的数字计算的时候,尽量不要超出最大值和最小值的范围,否则不准确

    • string类型

      • 字符串字面量(普通字符串)与模板字符串的区别

        模板字符串(ES6新增语法)可以实现换行,可以进行插值操作

      • 字符串方法的返回值类型

        许多字符串的方法返回的是一个新的字符串,而不是修改原始的字符串

      • 字符串拼接

        当使用+运算符拼接字符串和其他类型的时候,TS会自动将其他类型先转换成字符串,然后进行拼接,如果其他值无法转换成字符串会报错

      • 字符串比较

        字符串比较可以使用相等运算符(=或!)和比较运算符(>、<、<=、>=),需要注意的是比较运算符是按照字典顺序比较。在比较字符串时,要考虑大小写敏感,默认区分大小写,如果不想区分大小写比较可以使用toUpperCase()或toLowerCase()方法转换后再比较

      • 字符串类型的安全性

        当一个函数希望接收一个字符串类型参数的时候,如果在实际调用函数的时候,传入了一些其他的类型,TS在编译阶段就会报错,尽量不要对一些不存在的字符进行索引或对空串进行遍历

    • boolean类型

      • 类型转换

        在JS或TS中,如false、0、“”、null、undefined、NaN会转换成false,其他都会被转换成true

    • 数组类型

      在TS中数组类型的定义由两部分组成,即元素类型[],数组类型的变量可以由数组字面量进行初始化

      • 数组类型声明

        可以使用类型注解明确数组的元素类型或泛型的方式声明数组的元素类型

        let arr: Array<string> = ['a','b','c'] //泛型
        
      • 数组操作

        对数组进行增删改查(push,pop,splice)和遍历(for,forEach,map)的时候,一定要注意元素的类型

      • 只读数组

        在TS中可以使用readonly关键字声明只读数组

        let arr: readonly number[] = [1, 2, 3]
        
      • 类型推断和联合类型数组

        如果数组中要包含多种类型的元素的时候,可以把数组定义成联合类型的数组

        let arr: (string | number)[] = ['a', 1, 2, 'b']
        
    • 对象类型

      在TS中,对象是由一系列的属性名对应的属性值组成的数据结构

      • 对象类型的声明

        必须包括所有属性的名称和对应的类型,对象类型的变量可以通过对象字面量进行初始化

        let obj: {
            name: string
            age: number
            gender: string
        } = {
            name: '张三'
            age: 18
            gender: '男'
        }
        

        在TS一般使用接口(interface)或类型别名(type alias)来明确对象的结构和类型

        // 使用接口
        interface Person {
            name: string
            age: number
            gender: string
        }
        let obj: Person = {
            name: '张三'
            age: 18
            gender: '男'
        }
        // 使用类型别名
        type PersonType = {
            name: string
            age: number
            gender: string
        }
        let obj: PersonType = {
            name: '张三'
            age: 18
            gender: '男'
        }
        
      • 可选属性

        可以在对象类型声明中定义某些属性为可选属性(可有可无),使用问号?表示

        interface Car {
            brand: string
            price: number
            color?: string
        }
        let car: Car = {
            brand: '尊界'
            price: 1000000
            // gender: '棕色'
        }
        
      • 只读属性

        可以使用readonly关键字声明某些属性为只读属性,一旦对象被创建后,这些属性是不可修改的

        interface Book {
            title: string
            readonly author: string
        }
        let book: Book = {
            title: '十万个为什么'
            readonly author: '黄老师'
        }
        book.author = '张三' // 报错
        
      • 对象方法类型

        一个对象里面不单止可以包含一些普通的单一属性,还可以包含方法(一般是在对象或者class里面的函数,而函数一般是在全局作用域中的)

        interface Cacular {
            add(a: number, b: number): number
        }
        let cacular: Cacular = {
            add: (a, b) => a + b
        }
        
      • 对象的扩展和继承

        可以使用接口继承来扩展对象的类型

        interface Animal {
            name: string
        }
        interface Dog extends Animal {
            breed: string
        }
        let dog: Dog = {
            name: 'Max'
            breed: 'wang wang wang'
        }
        
      • 对象字面量的类型检查

        一个对象中必须包含接口中定义的属性(非可选属性),多一个少一个都不行,在编译阶段就会报错

  • 函数

    • 普通函数

      使用function关键字进行定义

      function sum(a: number, b: number): number{
          return a + b
      }
      

      特点:

      • 可以有参数和返回值,参数和返回值都可以指定类型
      • 一般用于执行特定的任务并返回结果
    • 匿名函数

      没有函数名的函数表达式

      const multiply = function (a: number, b: number): number{
          return a * b
      }
      

      特点:

      • 通常作为变量的值或者参数传递给其他函数
      • 通过变量名进行调用
    • 箭头函数

      使用箭头符号(=>)进行定义

      const divide = (a: number, b: number): number => {
          return a / b
      }
      

      简化(只保留参数列表(只有一个参数的时候才可把小括号去掉)和函数体(只有一行表达式的时候才可以把花括号和return去掉)这两个核心的部分):

      const arrow = message => message: ${message}
      

      特点:

      • 语法非常简洁
      • 自动绑定this值,避免了传统函数中this指向混乱的问题(普通函数this的指向取决于函数的调用方式,在严格模式下this默认指向的是undefined,在非严格模式下this可能指向全局对象或者调用这个函数的对象;箭头函数本质上没有自己的this,它会继承外层代码块的this)
      • 特别适合用在回调函数、数组处理方法等场景中
    • 方法

      在对象或者类中定义的函数称为方法

      class Cacular {
          add(a: number, b: number): number {
              return a + b
          }
      }
      

      特点:

      • 方法可以与类的实例或者对象相关联,可以访问类里面的属性和其他方法
      • 方法可以有不同的访问修饰符(public,private,protected)来控制其可见性和可访问性
    • 构造函数

      在类中用于创建实例的特殊方法,方法名与类名相同

      class Person {
          name: string
          
          constructor(name: string) {
              this.name = name
          }
      }
      

      特点:

      • 在创建类的实例时自动调用,用来初始化对象的属性
      • 构造函数可以有参数,用于接收初始值
    • 泛型函数

      使用类型参数来使得函数可以适应多种不同的类型

      function identity<T>(arg: T): T {
          return arg
      }
      
      console.log(identity<string>('Hello World'))
      

      特点:

      • 泛型函数提高了函数的灵活性和可复用性,可以根据不同的类型参数生成不同类型的安全函数
    • 参数

      • 可选参数

        可选参数就是通过在参数名后面加一个?进行识别

        function getPersonInfo(name: string, age: number, gender ?: string): string{
            // undefined表示一个对象或变量根本不存在
            // null表示一个对象或变量定义了,但是值为空
            if(gender === undefined) {
                gender = '未知'
            }
            return name: ${name}, age: ${age}, gender: ${gender}
        }
        
        console.log(getPersonInfo('张三', 18, '男'))
        console.log(getPersonInfo('张三', 20)) // gender: 未知
        
      • 默认参数

        function getPersonInfo(name: string, age: number, gender : string = '未知'): string{
            return name: ${name}, age: ${age}, gender: ${gender}
        }
        

        注意事项:

        1. 同一个参数不可即作为可选参数又作为默认参数
        2. 可选参数和默认参数一定要放在参数列表最后
        3. 一般情况下可选参数和默认参数只能有一个
      • 联合类型参数

        一个函数可能用于处理不同类型参数,这个情况特别适合使用联合类型参数

        function printNumberOrString(message: number | string){
            console.log(message)
        }
        
        printNumberOrString(100)
        printNumberOrString('Hello')
        
      • 任意类型参数

        如果函数需要处理任意类型的参数,则可以使用any类型

        function print(message: any){
            console.log(message)
        }
        
        print(100)
        print('Hello')
        print(true)
        print([1, 2, 3, 4])
        
    • 返回值

      • 返回值为空

        如果函数没有返回值,则可以使用void作为返回值类型

        function test(): void {
            console.log('Hello')
        }
        
  • 类的定义:

    class Person {
        // 属性
        id: number
        name: string
        age: number
        
        // 构造器
        constructor(id: number, name: string, age: number) {
            this.id = id
            this.name = name
            this.age = age
        }
        
        // 方法
        introduce(): string {
            return `Hello, I am ${this.name}, and my id is ${this.id}, ${this.age} years old.`
        }
    }
    
    let person = new Person(1, '张三', 18)
    

    new操作符实际上做了什么工作:

    1. 调用类里面的构造器,创建一个新的空对象,并且会继承构造器原型的属性和方法
    2. 构造器会被执行,并将新创建的对象作为this的值传入构造器中,通过this来访问并初始化属性和方法
    3. 新创建的对象的原型会被设置为构造器的原型对象,新创建的对象就可以访问构造器里面的属性和方法
    4. 最后,返回一个创建好的对象实例
    • 静态成员

      静态成员包括静态属性和静态方法,属于类本身,而不属于任何一个对象,一般用于定义一些常量和工具方法

      class Constants {
          // 静态属性
          static count: number = 1
      }
      
      class Utils {
          // 静态方法
          static toLowerCase(str: string) {
              return str.toLowerCase()
          }
      }
      
    • 继承

      允许一个类(子类或派生类)去继承另外一个类(父类或基类)属性和方法,子类就可以直接使用父类中的属性和方法,还可以添加一些新的特性

      class Student extends Person {
          classNumber: string
      
          constructor(id: number, name: string, age: number, classNumber: string) {
              super(id, name, age)
              this.classNumber = classNumber
          }
      
          introduce(): string {
              return super.introduce + `, I am a student.`
          }
      }
      

      注意:

      • 类的继承需要使用extends关键字
      • 子类构造器中需要使用super()调用父类的构造器
      • 子类中可以使用super关键字调用父类的方法
      • 子类中可以使用this关键字访问父类的属性和方法
    • 访问修饰符

      • public(公共)(默认):可以在任何地方访问属性和方法
      • private(私有):只能在当前类中访问属性和方法
      • protected(受保护的):只能在当前类和子类中访问属性和方法
    • 接口

      接口是面向对象编程中另外一个重要的概念,通常会作为一种契约或者规范让类遵守。接口中只会包含属性和方法的类型,而不会包含具体的实现细节,具体的细节交给类去实现

      interface Person {
          id: number
          name: string
          age: number
          introduce(): void
      }
      
      class Student implements Person {
          id: number
          name: string
          age: number
      
          constructor(id: number, name: string, age: number) {
              this.id = id
              this.name = name
              this.age = age
          }
      
          introduce(): void {
              console.log('Hello, I am a student')
          }
      }
      
    • 多态

      多态可以使得同一类型的对象具有不同的行为

      interface Person {
          id: number
          name: string
          age: number
          introduce(): void
      }
      
      class Student implements Person {
          id: number
          name: string
          age: number
      
          constructor(id: number, name: string, age: number) {
              this.id = id
              this.name = name
              this.age = age
          }
      
          introduce(): void {
              console.log('Hello, I am a student.')
          }
      }
      
      class Teacher implements Person {
          id: number
          name: string
          age: number
      
          constructor(id: number, name: string, age: number) {
              this.id = id
              this.name = name
              this.age = age
          }
      
          introduce(): void {
              console.log('Hello, I am a teacher.')
          }
      }
      
      let p1: Person = new Student(1, '张三', 18)
      let p2: Person = new Teacher(2, '李四', 40)
      
      p1.introduce() // Hello, I am a student.
      p2.introduce() // Hello, I am a teacher.
      
  • 枚举

    枚举是一种数据结构,通常用于定义一组有限的选项。在TS中,枚举实际上是一个对象,每个枚举的值都是该对象的一个属性,并且每个属性都有具体的值,属性值只支持两种类型——数字(默认,从0开始递增)和字符串

    enum Season {
        SPRING,
        SUMMER,
        AUTUMN,
        WINTER
    }
    

    枚举使用:

    1. 枚举值的访问就跟对象访问属性一样(Season.SPRING)
    2. 枚举值的类型其实就是enum的名称,let spring: Season = Season.SPRING
  • 模块化

    每个模块都有自己的作用域,在一个模块中定义的任何内容,在其他模块中是不可见的,如果要用其他模块里面的内容,可以使用导入导出

    // ./moduleA
    // 导出函数
    export function hello() {
        console.log('hello module A')
    }
    
    // 导出常量
    export const str: string = 'hello world'
    
    // ./moduleB
    // 导入
    import { hello, str } from './moduleA'
    

    若遇多模块命名冲突,可在导入重命名或创建模块对象

    // 导入重命名
    import { hello as hello_A , str as str_A } from './moduleA'
    
    // 创建模块对象
    import * as A from './moduleA'
    A.hello() // 调用
    

ArkTS快速入门

ArkTS 作为鸿蒙生态主推的应用开发语言,采用声明式编程范式,核心思想是“描述状态与界面的映射关系”,而非手动操作DOM(如命令式开发)。其开发流程可概括为以下三步:

  1. 定义界面状态

    界面状态是驱动UI变化的核心数据,通过@State@Link@Prop等装饰器声明(类似Vue的响应式数据)

    @Component
    struct Index {
      // 定义一个状态
      @State isOn: boolean = false
    }
    
  2. 描述界面显示效果

    基于声明的状态,通过简洁的UI组件组合描述界面结构(类似HTML+CSS,但更类型安全)

    build() {
        // 外壳容器
        Column(){
          if (this.isOn) {
            // 开灯图片组件
            Image($r('app.media.light'))
              .width('750px')
              .height('750px')
              .borderRadius(15)
          }else {
            // 关灯图片组件
            Image($r('app.media.dark'))
              .width('750px')
              .height('750px')
              .borderRadius(15)
          }
    
  3. 改变状态

    当状态变化时,框架自动重新渲染相关UI,无需手动操作DOM

    Row({
            space: 50
          }) {
            // 按钮组件
            Button('关灯')
              .backgroundColor(Color.Red)
              .onClick(() => {
                this.isOn = false
              })
            Button('开灯')
              .onClick(() => {
                this.isOn = true
              })
          }
    

控制渲染:

  • 条件渲染

    基于状态值决定渲染哪部分UI,支持if-elsematch语句(类似React的条件渲染)

    @Component
    struct Index {
      // 定义一个状态
      @State isRunning: boolean = false
    
      build() {
        // 外壳容器
        Column() {
          if (this.isRunning) {
            // 图片组件
            Image($r('app.media.ic_play'))
              .width(100)
              .height(100)
              .onClick(() => {
                this.isRunning = false
              })
          }else {
            // 图片组件
            Image($r('app.media.ic_pause'))
              .width(100)
              .height(100)
              .onClick(() => {
                this.isRunning = true
              })
          }
        }
        .width('100%')  // 宽度撑满整个屏幕
        .height('100%')  // 高度撑满整个屏幕
        .justifyContent(FlexAlign.Center)  // 居中
      }
    }
    
  • 循环渲染

    通过ForEach组件遍历数组,批量生成相同结构的子组件(需为每个子组件指定唯一key

    @Component
    struct Index {
      // 定义一个数组
      @State options: string[] = ['苹果', '桃子', '香蕉', '橘子']
      // 定义一个字符串
      @State answer: string = "____?"
    
      build() {
        // 外壳容器
        Column({
          space: 20
        }) {
          Row() {
            // 文本组件
            Text('你最喜欢的水果是')
              .fontSize(25)
              .fontWeight(FontWeight.Bold)
            Text(this.answer)
              .fontSize(25)
              .fontColor(Color.Green)
          }
          // ForEach(
          //   arr: any[],
          //   itemGenerator: (item: any, index?: number) => void,
          //   keyGenerator? (item: any, index?: number) => string
          // )
          // keyGenerator: 生成函数,为数组中的每个数组项生成唯一的表示key
          ForEach(this.options, (item: string) => {
            // 按钮组件
            Button(item)
              .width(100)
              .backgroundColor(Color.Green)
              .onClick(() => {
                this.answer = item
              })
          })
        }
        .width('100%')  // 宽度撑满整个屏幕
        .height('100%')  // 高度撑满整个屏幕
        .justifyContent(FlexAlign.Center)  // 居中
      }
    }
    
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐