harmonyOS一:基本語法

基本語法

文件構成

一個文件以 __.ets__結尾的,基本上要包含

  1. 裝飾器

    1. @Entry
    2. @Component
    3. @builder
    4. @State
    5. ...
  2. 自定義組件
    用@Component裝飾的struct Index

  3. 變量聲明

  4. UI描述
    build方法裏包含的代碼塊

  5. 系統組件

    ArkUI框架中默認內置的基礎和容器組件,可直接被開發者調用。
    就是ArkTS所支持的語法,類似於html中的標籤(span,div,p,image),

  6. 屬性方法
    css屬性,鏈式調用,看以下例子

  7. 事件方法

@Entry  // 裝飾器
@Component // 裝飾器  自定義組件聲明
struct Index{ // 自定義組件
 @State message: string = 'hello arkTS' // 裝飾器 變量聲明
 private color:string = 'red' // 變量聲明
 build(){ // UI描述
  Column(){ // 系統組件
   Text(this.message) // 系統組件
    .fontSize(20)
    .fontWeight(400)
    .onClick(()=>{}) // 事件方法
  }
  .height(100) // 屬性方法
 }
}
裝飾器
@Entry

入口文件的標識,@Entry裝飾的自定義組件將作爲UI頁面的入口。在單個UI頁面中, 最多可以使用@Entry裝飾一個自定義組件

只能有一個build函數,其build()函數下的根節點唯一且必要,且必須爲容器組件,其中ForEach禁止作爲根節點

@Component

@Component裝飾器僅能裝飾struct關鍵字聲明的數據結構。struct被@Component裝飾後具備組件化的能力,需要實現build方法描述UI,一個struct只能被一個@Component裝飾,

一個Component代表一個組件。

@Builder

自定義構建函數,相當於@Component裏build()的代碼塊,遵遁build()的函數語法規則,方便複用

遵遁UI語法法則

// 自定義組件內自定義構建函數
@Component
struct Index{
  @Builder
  ItemBuilder(){
    Row(){
      Text('builder')
      Text('hello')
    }
  }
  build(){
   Column(){
    this.ItemBuilder()
   }
  }
}
// 全局自定義構建函數
@Builder
function ItemBuilder(){
  Row(){
    Text('builder')
    Text('hello')
  }
}
@Component
struct Index{
  build(){
   Column(){
    ItemBuilder()
   }
  }
}

自定義組件內自定義構建函數和全局構建函數的區別:

  1. 在自定義組件內定義一個或多個@Builder方法,該方法被認爲是該組件的私有、特殊類型的成員函數,在自定義函數體中,this指代當前所屬組件,組件的狀態變量可以在自定義構建函數內訪問。建議通過this訪問自定義組件的狀態變量而不是參數傳遞
  2. 全局的自定義構建函數可以被整個應用獲取,不允許使用this和bind方法
  3. 如果不涉及組件狀態變化,建議使用全局的自定義構建方法,若是要大量使用組件內的變量,可採用自定義組件內自定義構建函數

參數傳遞:

  1. 按引用傳值
    傳遞的參數可爲狀態變量,且狀態變量的改變會引起UI重新渲染。ArkUI提供$$作爲按引用傳遞參數的範式

    @Builder function MyBuilder($$: {msg: string, num: string}){
     Row(){
      Text($$.msg)
      Text($$.num)
     }
    }
    @Component
    struct Index{
     @State msg:string = 'hello'
     @State num:number = 1
     build(){
      Column(){
       MyBuilder({msg: this.msg, num: this.num})
      }
     }
    }
    

    注意點:裏面的參數類型只能是string | Resource

  2. 按值傳值
    普通傳參數即可,若傳的參數爲狀態變量的話,不會引起UI重新渲染

@BuilderParam

???

@Styles

樣式複用,類似於css的類名所創建的css代碼塊

// 全局
@Styles function globalFancy  () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封裝的樣式
      Text('FancyA')
        .globalFancy ()
        .fontSize(30)
    }
  }
}
// 組件內
@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('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

注意點:

  1. 不支持參數傳遞
  2. 全局無法根據常量和狀態變量去訪問,組件內可以通過this去訪問常量和狀態變量
  3. 組件內@Styles高於全局@Styles,組件內找不到會找全局
  4. 全局聲明時需要function,組件內不需要
@Extend

擴展樣式,在@Styles基礎上擴展__原生組件樣式__,意思就是__必須要有原生組件__支撐,比如Column,Row,Text等等,比@Styles更強大

使用規則:

  1. 僅支持全局

  2. @Extend支持封裝指定的組件的私有屬性和私有事件和預定義相同組件的@Extend的方法

    // @Extend(Text)可以支持Text的私有屬性fontColor
    @Extend(Text) function TextExtends () {
      .fontColor(Color.Red)
    }
    // superTextExtends可以調用預定義的TextExtends
    @Extend(Text) function superTextExtends(size:number) {
        .fontSize(size)
        .TextExtends()
    }
    
  3. @Extend裝飾的方法支持參數/狀態變量,開發者可以在調用時傳遞參數,調用遵循TS方法傳值調用,狀態變量可以引起重新渲染

    @Extend(Text) function TextExtend (fontSize?: number,fontWeight?: number) {
      .fontColor(Color.Red)
      .fontSize(fontSize)
      .fontWeight(fontWeight)
    }
    
    @Entry
    @Component
    struct FancyUse {
      @State fontWeight:number = 100
      build() {
        Column(){
          Row({ space: 10 }) {
            Text('Fancy')
              .TextExtend()
            Text('Fancy')
              .TextExtend(24)
            Text('Fancy')
              .TextExtend(32,this.fontWeight)
          }
          Button('click')
            .onClick(()=>{
              this.fontWeight = 600
            })
        }
      }
    }
    
  4. @Extend裝飾的方法的參數可以爲function,作爲Event事件的句柄

    @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))
        }
      }
    }
    
stateStyles

stateStyles可以依據組件的內部狀態的不同,快速設置不同樣式

css僞類?stateStyles是屬性方法,可以根據UI內部狀態來設置樣式,類似於css僞類,有四種狀態

  1. focused 獲取焦點
  2. normal 正常
  3. pressed 按壓
  4. disabled 禁止
@Entry
@Component
struct UlImage{
  @State clickColor: string = 'red'
  @Styles pressedStyle() {
    .backgroundColor(Color.Green)
  }
  @State focusedColor: Color = Color.Red;
  build(){
    Column(){
      Text('111')
      Row(){
        Button('Click me')
          .stateStyles({
            focused: {
              .backgroundColor(this.focusedColor)
            },
            pressed: this.pressedStyle,
            normal: {
              .backgroundColor(Color.Yellow)
            }
          })
          .onClick(() => {
            this.focusedColor = Color.Pink
          })
      }

      Button('stateStyles')
        .stateStyles({
          disabled: {
            .backgroundColor(Color.Pink)
          }
        })
    }
  }
}

stateStyles裏是對象形式,一直都是focues模式?展現不了normal狀態??

@State

@State裝飾的變量,或稱爲狀態變量,一旦變量擁有了狀態屬性,就和自定義組件的渲染綁定起來。當狀態改變時,UI會發生對應的渲染改

@State聲明的變量是私有屬性,只能從組件內部訪問,聲明時必須指定類型和初始值

能導致ui重新渲染的方式:

  1. 基本數據類型: string,number,boolean

  2. 當裝飾的對象是array時,可以觀察到數組本身的賦值和添加、刪除、更新數組的變化,但深一級的觀察不到,看例子

    @Entry
    @Component
    struct UlImage{
      @State arr: Array<{name: string, age: number}> = [{
        name: 'peter',
        age: 18
      },{
        name: 'peter',
        age: 18
      },{
        name: 'peter',
        age: 18
      }]
      build(){
        Column(){
          Text('111')
          ForEach(this.arr, item=>{
            Row({space:10}){
              Text(item.name)
              Text(item.age + '')
            }
          })
    
          Row(){
            Button('Click me')
              .onClick(() => {
               this.arr.push({name: 'jerry', age: 22}) // 這個能能觸發渲染
                this.arr[1] = { // 這個能能觸發渲染
                  name: 'tom',
                  age: 20
                }
                this.arr[1].name = 'tom' // 這個觸發不了渲染
              })
          }
      }
    }
    
    
  3. 當裝飾的數據類型爲class或者Object時,可以觀察到自身的賦值的變化,和其屬性賦值的變化,即Object.keys(observedObject)返回的所有屬性,同樣的道理,但深一級的觀察不到,看例子

    class Name {
      public value: string;
    
      constructor(value: string) {
        this.value = value;
      }
    }
    
    class Model {
      public value: string;
      public name: Name;
      constructor(value: string, a: Name) {
        this.value = value;
        this.name = a;
      }
    }
    @Entry
    @Component
    struct UlImage{
      @State title: Model = new Model('Hello', new Name('World'));
      build(){
        Column(){
          Text(this.title.value)
          Text(this.title.name.value)
          Row(){
            Button('Click me')
              .onClick(() => {
                // this.arr.push({name: 'jerry', age: 22}) // 能變
                // this.title = new Model('Hi', new ClassA('ArkUI')); // 能變
                // this.title.value = 'Hi'; // 能變
                this.title.name.value = 'ArkUI'; // 不能變
              })
          }
        }
      }
    }
    
    @Component
    struct OP{
      message:string = '00'
      build(){
        Column(){
          Text(this.message)
        }
      }
    }
    
    

__如何實現深一級的觀察呢??__答案:@Observe/@ObjectLink

@Prop

單向同步,父組件流向子組件,類似於vue的prop,但不同的是@Prop是可操作的,比如修改

@Prop裝飾的變量是可變的,子組件變化不會同步回其父組件,就是@Prop變量允許在子組件修改,但修改後的變化不會同步回父組件,父組件的改變會同步給子組件

注意:@Prop裝飾器不能在@Entry裝飾的自定義組件中使用

@Prop沒有初始值,都是從父組件那裏傳來,父組件可以是常規變量,@State,@Link,@Prop...

支持的傳參類型:string,number,boolean,enum

@Entry
@Component
struct Index1{
  msg: string = 'hello world'
  @State name:string = 'peter'
  @State age: number = 18
  isMale: boolean = true
  build(){
    Column(){
      Hello({msg: this.msg,name: this.name,age: this.age, isMale: this.isMale})
      Button('click')
        .onClick(()=>{
          this.msg = 'hello arkTs'
          this.name = 'tom'
          this.age = 30
          this.isMale = false
        })
    }
  }
}

@Component
struct Hello{
  @Prop msg: string
  @Prop name: string
  @Prop age: number
  @Prop isMale: boolean
  build(){
    Column(){
      Text(this.msg)
      Text(this.name)
      Text(this.age + '')
      Text(this.isMale + '')
      HelloHello({msg: this.msg + 'today'})
    }
  }
}

@Component
struct HelloHello{
  @Prop msg: string
  build(){
    Column(){
      Text(this.msg)
    }
  }
}

雙向同步,兩方修改都會同步到另一方

注意:@Prop裝飾器不能在@Entry裝飾的自定義組件中使用

@Link沒有初始值,都是從父組件那裏傳來,父組件可以是@State,@Link,@Prop...,不包含常量,接收方式:@Link子組件從父組件初始化@State的語法爲Comp({ aLink: this.aState })和Comp({aLink: \(aState}),(目前試的情況只有\)aState,這個this.aState會報錯)

支持的傳參類型:Object、class、string、number、boolean、enum,其中Object,class,array的檢測方式和 @State一樣,更深一級就無法檢測到

原理:1. @Link的數據源的更新:子組件@Link包裝類把當前this指針註冊給父組件。父組件@State變量變更後,會遍歷更新所有依賴它的系統組件(elementid)和狀態變量(比如@Link包裝類),通知@Link包裝類更新後,子組件中所有依賴@Link狀態變量的系統組件(elementId)都會被通知更新2. 當子組件中@Link更新:@Link更新後,調用父組件的@State包裝類的set方法,將更新後的數值同步回父組件,子組件@Link和父組件@State分別遍歷依賴的系統組件,進行對應的UI的更新

有個bug?看例子

class Age{
  value:number
  constructor(value:number) {
    this.value = value
  }
}
class Person{
  name: string
  age: Age
  constructor(name: string, age: Age) {
    this.name = name
    this.age = age
  }
}

@Entry
@Component
struct LinkIndex{
  @State msg:string = 'hello world'
  @State person: Person = new Person('peter', new Age(18))
  build(){
    Column(){
      LinkChildren({message: $msg,msg: this.msg, person: $person})
      Button('click')
        .onClick(()=>{
          this.msg = 'hello arkTs'
          // this.person.name = 'tom' // 單個的情況下不會渲染age,會渲染name
          this.person.age.value = 30 //  // !bug!!!!!!!當name和age.value同時更改時,都會重新渲染
        })
    }
  }
}

@Component
struct LinkChildren{
  @Link message: string
  @Prop msg:string
  @Link person: Person
  build(){
    Column(){
      Text('[LinkChildren] ' + this.message)
      Text('[LinkChildren] ' + this.msg)
      Row({space:10}){
        Text(this.person.name)
        Text(this.person.age.value + '')
      }
      LinkChildrenChildren({message: $message, msg: $msg})
      Button('LinkChildren click')
        .onClick(()=>{
          // this.person.age.value = 100 // 單個的情況下不會渲染age,會渲染name
          this.person.name = 'jerry' // !bug!!!!!!!當name和age.value同時更改時,都會重新渲染
        })
    }
  }
}

@Component
struct LinkChildrenChildren{
  @Link message: string
  @Link msg: string
  build(){
    Column(){
      Text('[LinkChildrenChildren] ' + this.message)
      Text('[LinkChildrenChildren] ' + this.msg)
    }
  }
}
@Provide/@Consume

與後代組件的雙向數據同步,類似於@Link的雙向數據同步,只不過省略了嵌套,改成用@Provide去聲明,@Consume去獲取去修改,擁有@Link的特性和參數傳遞的方式

@Provide可以在@Entry聲明,有初始值,@Consume也可以在子組件中聲明

class Age{
  value:number
  constructor(value:number) {
    this.value = value
  }
}
class Person{
  name: string
  age: Age
  constructor(name: string, age: Age) {
    this.name = name
    this.age = age
  }
}

@Entry
@Component
struct LinkIndex{
  @Provide msg:string = 'hello world'
  @Provide person: Person = new Person('peter', new Age(18))
  build(){
    Column({space:20}){
      LinkChildren()
      Text(this.msg)
      Button('click')
        .onClick(()=>{
          this.msg = 'hello arkTs~~~~~~'
          this.person.name = 'tom'
          // this.person.age.value = 30// !bug!!!!!!!當name和age.value同時更改時,都會重新渲染
        })
    }
  }
}

@Component
struct LinkChildren{
  @Consume msg:string
  build(){
    Column({space:20}){
      Text('[LinkChildren] ' + this.msg)
      LinkChildrenChildren()
      Button('LinkChildren click')
        .onClick(()=>{
          // this.person.age.value = 100
          this.msg = 'hello arkTs'
        })
    }
  }
}

@Component
struct LinkChildrenChildren{
  @Consume person: Person
  build(){
    Column({space:20}){
      Text('[LinkChildrenChildren] ' + this.person.name)
      Text('[LinkChildrenChildren] ' + this.person.age.value)
      Button('LinkChildrenChildren click')
        .onClick(()=>{
          // this.person.age.value = 100
          this.person.name = 'jerry'// !bug!!!!!!!當name和age.value同時更改時,都會重新渲染
        })
    }
  }
}

使用@Observed裝飾class會改變class原始的原型鏈,@Observed和其他類裝飾器裝飾同一個class可能會帶來問題
@ObjectLink裝飾器不能在@Entry裝飾的自定義組件中使用

雙向數據變化同步__? ?__

  • 被@Observed裝飾的類,可以被觀察到屬性的變化;
  • 子組件中@ObjectLink裝飾器裝飾的狀態變量用於接收@Observed裝飾的類的實例,和父組件中對應的狀態變量建立雙向數據綁定。這個實例可以是數組中的被@Observed裝飾的項,或者是class object中的屬性,這個屬性同樣也需要被@Observed裝飾

注意:

  1. @ObjectLink裝飾的變量不能被賦值,如果要使用賦值操作,請使用@Prop

  2. @ObjectLink裝飾的數據是可讀的,只能允許數據屬性賦值,而不能對自身賦值

使用方法:將要監聽的屬性單獨用@Component自定義組件,然後將要監聽的屬性用@ObjectLink裝飾,這樣父組件或子組件修改二級屬性都會更改渲染,看例子

// object的二級屬性更改渲染
@Observed class Sex{
  value: string;

  constructor(sex: string) {
    this.value = sex
  }
}
@Observed class Person {
  name: string;
  age: number;
  sex?: Sex
  constructor(name: string, age: number,sex?: Sex) {
    this.name = name;
    this.age = age;
    this.sex = sex
  }
}
@Entry
@Component
struct UIImage{
  @State person: Person = {
    name: 'peter',
    age: 18,
    sex: new Sex('male')
  }
  build(){
    Column({space:10}){
      Text(this.person.name)
      Text(this.person.age + '')
      Text(this.person.sex.value)//這個不變,怎麼會是雙向的呢????????
      // Text(this.person.sex.value)
      Children({sex: this.person.sex})
      Button('click')
        .onClick(()=>{
          // this.person.name = 'tom'
          this.person.sex.value = this.person.sex.value === 'male' ? 'female':'male'
        })
    }
  }

}
@Component
struct Children{
  @ObjectLink sex: Sex
  build(){
    Column(){
      Text(this.sex.value + '')
    }
  }
}
// array的二級屬性更改渲染
@Observed
class Item {
  name: string;
  age: number

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age
  }
}
@Observed
class ListData extends Array<Item>{}
@Entry
@Component
struct UlImage {
  @State arr: ListData = new ListData(new Item('peter', 18),new Item('peter', 18),new Item('peter', 18))

  build() {
    Column() {
      Text('111')
      ForEach(this.arr, item => {
        It({item})
        // Row({ space: 10 }) {
        //   Text(item.name)
        //   Text(item.age + '')
        // }
      })

      Row() {
        Button('Click me')
          .onClick(() => {
            // this.arr.push({ name: 'jerry', age: 22 }) // 這個能能觸發渲染
            // this.arr[1] = { // 這個能能觸發渲染
            //   name: 'tom',
            //   age: 20
            // }
            this.arr[1].name = 'tom' // 這個觸發不了渲染
          })
      }
    }
  }
}
@Component
struct It{
  @ObjectLink item: Item
  build(){
    Column(){
      Row({ space: 10 }) {
        Text(this.item.name)
        Text(this.item.age + '')
          .onClick(()=>{
            this.item.age = 100
          })
      }
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章