鴻蒙HarmonyOS實戰-ArkTS語言(基本語法)

蜀道山QAQ發表於2024-03-20

🚀一、ArkTS語言基本語法

🔎1.簡介

HarmonyOS的ArkTS語言是一種基於TypeScript開發的語言,它專為HarmonyOS系統開發而設計。ArkTS語言結合了JavaScript的靈活性和TypeScript的嚴謹性,使得開發者能夠快速、高效地開發出高質量的HarmonyOS應用程式。

ArkTS語言具有以下特點:

靜態型別檢查:開發者在編寫程式碼時可以使用型別註解來進行型別檢查,從而減少因型別錯誤而導致的bug。

非同步/同步程式設計:ArkTS語言支援基於Promise和async/await的非同步/同步程式設計方式,能夠更好地處理非同步操作。

內建模組:ArkTS語言內建了許多常用的模組,如檔案系統、網路請求、圖形渲染等,使得開發者不必自己編寫這些模組。

相容性:ArkTS語言使用TypeScript語法,可以與JavaScript程式碼無縫整合,並且可以編譯成JavaScript程式碼來在其他平臺上執行。

ArkTS語言基礎類庫是HarmonyOS系統上為應用開發者提供的常用基礎能力,主要包含能力如下圖所示:
image

ArkTS是HarmonyOS優選的主力應用開發語言。ArkTS圍繞應用開發在TypeScript(簡稱TS)生態基礎上做了進一步擴充套件,繼承了TS的所有特性,是TS的超集。

ArkTS和HTML的差別:

image

image

🔎2.TypeScript的基礎語法

TypeScript是一種由微軟開發的JavaScript超集語言,它支援JavaScript的所有語法,但新增了一些新的特性和語法,使開發更加可靠和高效。TypeScript最大的特點是引入了靜態型別,開發者可以在編譯時發現型別錯誤,提高程式碼的可維護性和可讀性。

TypeScript程式碼可以在編譯時被轉換成JavaScript程式碼,在瀏覽器和Node.js環境下都可以執行。雖然TypeScript相對於JavaScript來說更加複雜,但是它可以幫助開發者更好地組織和管理複雜的專案,特別是在團隊協作中提高程式碼的質量和可維護性。

TypeScript基礎知識包括基本型別、變數宣告、函式、類、介面、泛型等。另外,TypeScript還支援模組化開發,可以使用ES模組規範或者CommonJS規範匯入和匯出模組。在實際專案開發中,TypeScript還可以結合工具鏈如Webpack、Babel進行編譯、打包等操作。

除了上面提到的變數宣告、函式定義、類定義、介面定義和列舉型別外,TypeScript還有一些基礎語法需要掌握:

🦋2.1 型別註解
TypeScript的靜態型別檢查是透過型別註解實現的。在宣告變數或函式時,可以使用冒號加上型別註解,指定變數或函式的型別。例如:

let name: string = "TypeScript";

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

🦋2.2 介面
TypeScript的介面是用來描述物件的形狀的。可以定義物件需要包含哪些屬性和方法,以及它們的型別。例如:

interface Person {
    name: string;
    age: number;
    sayHello(): void;
}

let tom: Person = {
    name: "Tom",
    age: 18,
    sayHello: function() {
        console.log(`Hello, my name is ${this.name}!`);
    }
};

🦋2.3 泛型
TypeScript的泛型可以幫助我們編寫更加靈活、可重用的程式碼。它允許在編寫函式、類或介面時使用引數化型別,從而提高程式碼的通用性和可讀性。例如:

function identity<T>(arg: T): T {

<details>
<summary>點選檢視程式碼</summary>

function identity(arg: T): T {
return arg;
}

let output = identity("TypeScript");
console.log(output); // 輸出 TypeScript

</details>

    return arg;
}

let output = identity<string>("TypeScript");
console.log(output); // 輸出 TypeScript

🦋2.4 類的繼承
TypeScript的類可以繼承其他類,從而實現程式碼的重用和擴充套件。透過關鍵字extends可以讓一個類繼承另一個類,並繼承其屬性和方法。例如:

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    move(distance: number = 0) {
        console.log(`${this.name} moved ${distance}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log("Woof! Woof!");
    }
}

let dog = new Dog("Bobby");
dog.move(10); // 輸出 "Bobby moved 10m."
dog.bark(); // 輸出 "Woof! Woof!"

🦋2.5 類的訪問修飾符
TypeScript的類可以透過訪問修飾符來控制類的屬性和方法的訪問許可權。有三個訪問修飾符可以使用:public、private和protected。預設情況下,都是public

public:公共的,任何外部或內部都可以訪問。

private:私有的,只有類的內部可以訪問,外部無法訪問。

protected:受保護的,只有類的內部和其子類可以訪問,外部無法訪問。

class Person {
    protected name: string;
    constructor(name: string) {
        this.name = name;
    }
    protected sayHello() {
        console.log(`Hello, I'm ${this.name}.`);
    }
}

class Student extends Person {
    constructor(name: string) {
        super(name);
    }
    public sayHelloToTeacher(teacher: Person) {
        console.log(`Hello, ${teacher.name}, I'm ${this.name}.`);
    }
}

let tom = new Student("Tom");
let bob = new Person("Bob");
tom.sayHelloToTeacher(bob); // 輸出 "Hello, Bob, I'm Tom."
bob.sayHello(); // 報錯:屬性 'sayHello' 受保護,只能在類 'Person' 及其子類中訪問。

以上只是舉例一些TS的基礎語法,TS內容遠不止這些不懂的可以去學學TS。

🔎3.ArkTS的基本組成

image

裝飾器:用於裝飾類、結構、方法以及變數,並賦予其特殊的含義。如上述示例中@Entry、@Component和@State都是裝飾器,@Component表示自定義元件,@Entry表示該自定義元件為入口元件,@State表示元件中的狀態變數,狀態變數變化會觸發UI重新整理。

UI描述:以宣告式的方式來描述UI的結構,例如build()方法中的程式碼塊。

自定義元件:可複用的UI單元,可組合其他元件,如上述被@Component裝飾的struct Hello。

系統元件:ArkUI框架中預設內建的基礎和容器元件,可直接被開發者呼叫,比如示例中的Column、Text、Divider、Button。

屬性方法:元件可以透過鏈式呼叫配置多項屬性,如fontSize()、width()、height()、backgroundColor()等。

事件方法:元件可以透過鏈式呼叫設定多個事件的響應邏輯,如跟隨在Button後面的onClick()。
系統元件、屬性方法、事件方法具體使用可參考基於ArkTS的宣告式開發正規化。

除此之外,ArkTS擴充套件了多種語法正規化來使開發更加便捷:

@Builder/@BuilderParam:特殊的封裝UI描述的方法,細粒度的封裝和複用UI描述。
@Extend/@Style:擴充套件內建元件和封裝屬性樣式,更靈活地組合內建元件。
stateStyles:多型樣式,可以依據元件的內部狀態的不同,設定不同樣式。

🔎4.自定義元件

@Component
struct HelloComponent {
  @State message: string = 'Hello, World!';

  build() {
    // HelloComponent自定義元件組合系統元件Row和Text
    Row() {
      Text(this.message)
        .onClick(() => {
          // 狀態變數message的改變驅動UI重新整理,UI從'Hello, World!'重新整理為'Hello, ArkUI!'
          this.message = 'Hello, ArkUI!';
        })
    }
  }
}

@Entry
@Component
struct ParentComponent {
  build() {
    Column() {
      Text('ArkUI message')
      HelloComponent({ message: 'Hello, World!' });
      Divider()
      HelloComponent({ message: '你好!' });
    }
  }
}

struct:自定義元件基於struct實現,struct + 自定義元件名 +
{…}的組合構成自定義元件,不能有繼承關係。對於struct的例項化,可以省略new。

build()函式:build()函式用於定義自定義元件的宣告式UI描述,自定義元件必須定義build()函式。

@Entry:@Entry裝飾的自定義元件將作為UI頁面的入口。在單個UI頁面中,最多可以使用@Entry裝飾一個自定義元件。@Entry可以接受一個可選的LocalStorage的引數。

🦋4.1 build()函式規範
1、根節點唯一

@Entry
@Component
struct MyComponent {
  build() {
    // 根節點唯一且必要,必須為容器元件
    Row() {
      ChildComponent() 
    }
  }
}

@Component
struct ChildComponent {
  build() {
    // 根節點唯一且必要,可為非容器元件
    Image('test.jpg')
  }
}

2、不允許宣告本地變數、列印、作用域

build() {
  // 反例:不允許宣告本地變數
  let a: number = 1;
  // 反例:不允許console.info
  console.info('print debug log');
  // 反例:不允許本地作用域
  {
    ...
  }
}

3、不允許呼叫沒有用@Builder裝飾的方法,允許系統元件的引數是TS方法的返回值。

@Component
struct ParentComponent {
  doSomeCalculations() {
  }

  calcTextValue(): string {
    return 'Hello World';
  }

  @Builder doSomeRender() {
    Text(`Hello World`)
  }

  build() {
    Column() {
      // 反例:不能呼叫沒有用@Builder裝飾的方法
      this.doSomeCalculations();
      // 正例:可以呼叫
      this.doSomeRender();
      // 正例:引數可以為呼叫TS方法的返回值
      Text(this.calcTextValue())
    }
  }
}

4、不允許switch和表示式

build() {
  Column() {
    // 反例:不允許使用switch語法
    switch (expression) {
      case 1:
        Text('...')
        break;
      case 2:
        Image('...')
        break;
      default:
        Text('...')
        break;
    }
    // 反例:不允許使用表示式
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}

🔎5.頁面和自定義元件生命週期

頁面生命週期,即被@Entry裝飾的元件生命週期,提供以下生命週期介面:

  • onPageShow:頁面每次顯示時觸發。
  • onPageHide:頁面每次隱藏時觸發一次。
  • onBackPress:當使用者點選返回按鈕時觸發。

元件生命週期,即一般用@Component裝飾的自定義元件的生命週期,提供以下生命週期介面:

  • aboutToAppear:元件即將出現時回撥該介面,具體時機為在建立自定義元件的新例項後,在執行其build()函式之前執行。
  • aboutToDisappear:在自定義元件即將析構銷燬時執行。
// Index.ets
import router from '@ohos.router';

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;

  // 只有被@Entry裝飾的元件才可以呼叫頁面的生命週期
  onPageShow() {
    console.info('Index onPageShow');
  }
  // 只有被@Entry裝飾的元件才可以呼叫頁面的生命週期
  onPageHide() {
    console.info('Index onPageHide');
  }

  // 只有被@Entry裝飾的元件才可以呼叫頁面的生命週期
  onBackPress() {
    console.info('Index onBackPress');
  }

  // 元件生命週期
  aboutToAppear() {
    console.info('MyComponent aboutToAppear');
  }

  // 元件生命週期
  aboutToDisappear() {
    console.info('MyComponent aboutToDisappear');
  }

  build() {
    Column() {
      // this.showChild為true,建立Child子元件,執行Child aboutToAppear
      if (this.showChild) {
        Child()
      }
      // this.showChild為false,刪除Child子元件,執行Child aboutToDisappear
      Button('create or delete Child').onClick(() => {
        this.showChild = false;
      })
      // push到Page2頁面,執行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/Page2' });
        })
    }

  }
}

@Component
struct Child {
  @State title: string = 'Hello World';
  // 元件生命週期
  aboutToDisappear() {
    console.info('[lifeCycle] Child aboutToDisappear')
  }
  // 元件生命週期
  aboutToAppear() {
    console.info('[lifeCycle] Child aboutToAppear')
  }

  build() {
    Text(this.title).fontSize(50).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}

🔎6.裝飾函式

🦋6.1 @Builder裝飾器

@Builder主要是定義頁面UI

☀️6.1.1 裝飾指向

1、自定義元件內自定義構建函式

@Builder MyBuilderFunction(){ ... }
#使用
this.MyBuilderFunction(){ ... }

2、MyGlobalBuilderFunction()

@Builder function MyGlobalBuilderFunction(){ ... }
#使用
MyGlobalBuilderFunction()

☀️6.1.2 引數傳遞

1、按引用傳遞引數

@Builder function ABuilder($$: { paramA1: string }) {
  Row() {
    Text(`UseStateVarByReference: ${$$.paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      // 在Parent元件中呼叫ABuilder的時候,將this.label引用傳遞給ABuilder
      ABuilder({ paramA1: this.label })
      Button('Click me').onClick(() => {
        // 點選“Click me”後,UI從“Hello”重新整理為“ArkUI”
        this.label = 'ArkUI';
      })
    }
  }
}

2、按值傳遞引數

@Builder function ABuilder(paramA1: string) {
  Row() {
    Text(`UseStateVarByValue: ${paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  label: string = 'Hello';
  build() {
    Column() {
      ABuilder(this.label)
    }
  }
}

🦋6.2 @BuilderParam裝飾器

@BuilderParam用來裝飾指向@Builder方法的變數,開發者可在初始化自定義元件時對此屬性進行賦值,為自定義元件增加特定的功能。

☀️6.2.1 裝飾指向

1、本地初始化@BuilderParam

@Builder function GlobalBuilder0() {}

@Component
struct Child {
  @Builder doNothingBuilder() {};

  @BuilderParam aBuilder0: () => void = this.doNothingBuilder;
  @BuilderParam aBuilder1: () => void = GlobalBuilder0;
  build(){}
}

2、初始化子元件@BuilderParam

@Component
struct Child {
  @BuilderParam aBuilder0: () => void;

  build() {
    Column() {
      this.aBuilder0()
    }
  }
}

@Entry
@Component
struct Parent {
  @Builder componentBuilder() {
    Text(`Parent builder `)
  }

  build() {
    Column() {
      Child({ aBuilder0: this.componentBuilder })
    }
  }
}

this都是器其本身,不會存在傳遞。

☀️6.2.2 使用場景

1、引數化傳遞

@Builder function GlobalBuilder1($$ : {label: string }) {
  Text($$.label)
    .width(400)
    .height(50)
    .backgroundColor(Color.Blue)
}

@Component
struct Child {
  label: string = 'Child'
  // 無引數類,指向的componentBuilder也是無引數型別
  @BuilderParam aBuilder0: () => void;
  // 有引數型別,指向的GlobalBuilder1也是有引數型別的方法
  @BuilderParam aBuilder1: ($$ : { label : string}) => void;

  build() {
    Column() {
      this.aBuilder0()
      this.aBuilder1({label: 'global Builder label' } )
    }
  }
}

@Entry
@Component
struct Parent {
  label: string = 'Parent'

  @Builder componentBuilder() {
    Text(`${this.label}`)
  }

  build() {
    Column() {
      this.componentBuilder()
      Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 })
    }
  }
}

2、尾隨閉包

// xxx.ets
@Component
struct CustomContainer {
  @Prop header: string;
  @BuilderParam closer: () => void

  build() {
    Column() {
      Text(this.header)
        .fontSize(30)
      this.closer()
    }
  }
}

@Builder function specificParam(label1: string, label2: string) {
  Column() {
    Text(label1)
      .fontSize(30)
    Text(label2)
      .fontSize(30)
  }
}

@Entry
@Component
struct CustomContainerUser {
  @State text: string = 'header';

  build() {
    Column() {
      // 建立CustomContainer,在建立CustomContainer時,透過其後緊跟一個大括號“{}”形成尾隨閉包
      // 作為傳遞給子元件CustomContainer @BuilderParam closer: () => void的引數
      CustomContainer({ header: this.text }) {
        Column() {
          specificParam('testA', 'testB')
        }.backgroundColor(Color.Yellow)
        .onClick(() => {
          this.text = 'changeHeader';
        })
      }
    }
  }
}

🦋6.3 @Styles裝飾器

@Styles裝飾器主要是定義公共樣式

☀️6.3.1 裝飾指向

1、全域性

// 全域性
@Styles function functionName() { ... }

// 在元件內
@Component
struct FancyUse {
  @Styles fancy() {
    .height(100)
  }
}

2、元件內

@Component
struct FancyUse {
  @State heightValue: number = 100
  @Styles fancy() {
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }
}

☀️6.3.2 使用場景

// 定義在全域性的@Styles封裝的樣式
@Styles function globalFancy  () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100
  // 定義在元件內的@Styles封裝的樣式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }

  build() {
    Column({ space: 10 }) {
      // 使用全域性的@Styles封裝的樣式
      Text('FancyA')
        .globalFancy ()
        .fontSize(30)
      // 使用元件內的@Styles封裝的樣式
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

🦋6.4 @Extend裝飾器
@Extend用於擴充套件原生元件樣式,作用和@Styles差不多。

☀️6.4.1 裝飾指向
@Extend僅支援定義在全域性,不支援在元件內部定義

1、@Extend支援封裝指定的元件的私有屬性和私有事件

// @Extend(Text)可以支援Text的私有屬性fontColor
@Extend(Text) function fancy () {
  .fontColor(Color.Red)
}
// superFancyText可以呼叫預定義的fancy
@Extend(Text) function superFancyText(size:number) {
    .fontSize(size)
    .fancy()
}

2、@Extend裝飾的方法支援引數

// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

@Entry
@Component
struct FancyUse {
  build() {
    Row({ space: 10 }) {
      Text('Fancy')
        .fancy(16)
      Text('Fancy')
        .fancy(24)
    }
  }
}

3、@Extend裝飾的方法的引數可以為function

@Extend(Text) function makeMeClick(onClick: () => void) {
  .backgroundColor(Color.Blue)
  .onClick(onClick)
}

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World';

  onClickHandler() {
    this.label = 'Hello ArkUI';
  }

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .makeMeClick(this.onClickHandler.bind(this))
    }
  }
}

4、@Extend的引數可以為狀態變數

@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

@Entry
@Component
struct FancyUse {
  @State fontSizeValue: number = 20
  build() {
    Row({ space: 10 }) {
      Text('Fancy')
        .fancy(this.fontSizeValue)
        .onClick(() => {
          this.fontSizeValue = 30
        })
    }
  }
}

☀️6.4.2 使用場景

@Extend(Text) function fancyText(weightValue: number, color: Color) {
  .fontStyle(FontStyle.Italic)
  .fontWeight(weightValue)
  .backgroundColor(color)
}

@Entry
@Component
struct FancyUse {
  @State label: string = 'Hello World'

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fancyText(100, Color.Blue)
      Text(`${this.label}`)
        .fancyText(200, Color.Pink)
      Text(`${this.label}`)
        .fancyText(300, Color.Orange)
    }.margin('20%')
  }
}

🔎7.多型樣式

stateStyles是屬性方法,可以根據UI內部狀態來設定樣式,類似於css偽類,但語法不同。ArkUI提供以下四種狀態:

  • focused:獲焦態
  • normal:正常態
  • pressed:按壓態
  • disabled:不可用態

🦋7.1 基本使用

@Entry
@Component
struct CompWithInlineStateStyles {
  @State focusedColor: Color = Color.Red;
  normalColor: Color = Color.Green
  build() {
    Column() {
      Button('clickMe').height(100).width(100)
        .stateStyles({
          normal: {
            .backgroundColor(this.normalColor)
          },
          focused: {
            .backgroundColor(this.focusedColor)
          }
        })
        .onClick(() => {
          this.focusedColor = Color.Pink
        })
        .margin('30%')
    }
  }
}

🦋7.2 @Styles和stateStyles聯合使用

@Entry
@Component
struct MyComponent {
  @Styles normalStyle() {
    .backgroundColor(Color.Gray)
  }

  @Styles pressedStyle() {
    .backgroundColor(Color.Red)
  }

  build() {
    Column() {
      Text('Text1')
        .fontSize(50)
        .fontColor(Color.White)
        .stateStyles({
          normal: this.normalStyle,
          pressed: this.pressedStyle,
        })
    }
  }
}

🦋7.3 stateStyles裡使用常規變數和狀態變數

@Entry
@Component
struct CompWithInlineStateStyles {
  @State focusedColor: Color = Color.Red;
  normalColor: Color = Color.Green

  build() {
    Button('clickMe').height(100).width(100)
      .stateStyles({
        normal: {
          .backgroundColor(this.normalColor)
        },
        focused: {
          .backgroundColor(this.focusedColor)
        }
      })
      .onClick(() => {
        this.focusedColor = Color.Pink
      })
      .margin('30%')
  }
}

相關文章