随笔

鸿蒙开发初接触

近俩月有点颓废了,之前是每天下班后都学点啥,但现在已经不是一个人了嘛,回到家就一起瘫着看小说刷视频,也没学啥新东西,做了个记账的小程序用着还挺好,想着看能不能挪到 HarmonyOS 中,顺便学点新鲜的东西。

Basic Example

@Component
export struct DetailComponent {
    build() {
        Column() {
            Text("Detail")
        }
    }
}
import { DetailComponent } from './DetailComponent.ets' // 导入外部自定义的组件

@Entry   // 标识为入口组件
@Component  // 标识为组件
struct MyPage {
    @State currentTabIndex: number = 0; // 声明状态
    @State listData: string[] = [];

    build() {
        Column() {
            this.Title() // 调用页面内自定义的组件

            Text("Hello World") // 内置组件
                .fontSize(16) // 设置属性
                .onClick(() => { // 事件
                  this.currentTabIndex = this.currentTabIndex + 1 // 更新状态
                })

            DetailComponent() // 调用外部自定义的组件

            if (this.currentTabIndex > 0) { // 逻辑判断
                Text("You clicked")
            }

            ForEach(this.listData, // 循环遍历
                (item: string, index?: number) => {
                    Text(item)
            })
    }
  }

    @Builder Title() {
        Row() {
            Text("Welcome")
        }
    }
}

State Management

  • @State 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
  • @Prop 接受来自父组件的状态,允许组件内部修改 @Prop 变量,但更改不会通知给父组件,并且只支持基本类型 string, number, or boolean
  • @Link 可以和父组件的 @State 变量建立双向数据绑定
  • @Provide 与 @Consume 支持在页面内跨层级数据传递
  • @Observed 和 @ObjectLink,如果深层次对象产生变化时 State 无法监控到,可以使用 @Observed 装饰变量类型,用 @ObjectLink 代替 @Link
@State count: number = 1
@State linkCount: number = 1
Child({ count: this.count, linkCount: $linkCount  }) // Prop 属性用 this.xxx 传递,Link 用 $xxx 传递

// Child.ets
@Prop count: number
@Link count: number

// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;

// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;

Deep change

单独讲一下深层次对象的变更,深层次对象即引用对象的引用对象产生了变更,比如 Person[] ,数组中对象的属性发生了变更。按照前端的经验一般拿到数据后声明个 interface 直接使用了,但是这里需要单独为这个数据创建一个 class

比如 React 应用中

interface Person {
  name: string;
  age: number;
}

let list: Person[] = data as Person[]

list.map(item => <span key={item}>{item}</span>)

先声明类型,然后传递到子元素

@Observed
class Person {
  public name: string;
  public age: number;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

// Top component
@State list: Person[] = [];
data.forEach(item => {
  list.push(new Person(item.name, item.age))
});

ArrList({ arrList: $arrList })

最终到内部类中,再使用 @ObjectLink

// List component
@Component
struct ArrList {
  @Link arrList: Person[]

  build() {
    Column() {
      ForEach(this.arrList, (item: Person) => { ArrItem({ arrItem: item }) })
    }
  }
}

// List item component
@Component
struct ArrItem {
  @ObjectLink arrItem: Person

  build() {
    Column() {
      Text(this.arrItem.name)
    }
  }
}

到这里看上去还好,但是很鸡肋的是就算用上 @Observed 也只能监控一层,再里面一层还是无法监控到,比如 Person 下有个 attributes: { pwere: number } 结构,这时候使用 this.arrList[0].attributes.power = 30 去更新数据是无效的操作,即便将 attributes 也创建成 @Observed 监控的 class 也会是无效,因为它只支持一次传递。

State Watch

@Provide @Watch('onProgressChanged') overAllProgressChanged: boolean = false;

onProgressChanged() {}

Date Storage

Preferences

适合设备配置项之类的存储

import dataPreferences from '@ohos.data.preferences';

await dataPreferences.getPreferences(context, PREFERENCES_NAME);

await preferences.get(KEY_APP_FONT_SIZE, fontSize);
await preferences.put(KEY_APP_FONT_SIZE, fontSize);
// delete, has

preferences.flush(); // 持久化到文件中

LocalStorage

生命周期跟页面相同,可以通过注入 windowStage.loadContent 时实现提升到 UIAbility 级别。

AppStorage

程序生命周期内使用,可以通过 PersistentStorage 选择特定属性进行持久化, AppStorage 的变化会自动同步到 PersistentStorage,程序重新启动时也会自动读取。

PersistentStorage.PersistProp('count', 100)

@Entry
@Component
struct MainPage {
  @StorageLink('count') count: number = 0
}

Lifecycle

1676685761072.png

按照图上流程,将数据请求放在 aboutToAppear 中即可

Router

import router from '@ohos.router';

router.pushUrl({
  url: 'pages/SecondPage'
})

Network Request

let url = "https://EXAMPLE_URL";
let promise = httpRequest.request(
// 请求url地址
url,
{
  // 请求方式
  method: http.RequestMethod.POST,
  // 请求的额外数据。
  extraData: {
    "param1": "value1",
    "param2": "value2",
  },
  // 可选,默认为60s
  connectTimeout: 60000,
  // 可选,默认为60s
  readTimeout: 60000,
  // 开发者根据自身业务需要添加header字段
  header: {
    'Content-Type': 'application/json'
  }
});

promise.then((data) => { 
  if (data.responseCode === http.ResponseCode.OK) {
    console.info('Result:' + data.result);
    console.info('code:' + data.responseCode);
  }
}).catch((err) => {
  console.info('error:' + JSON.stringify(err));
});

Layout

Row, Col 等同于 flex 中的行和列布局

List, ListItem 列表

Grid, GridItem 网格布局

RelativeContainer 相对布局

Stack 重叠布局

Size

vp/fp 概念上等同于移动开发时的 rem, rem 的大小需要我们自己根据屏幕计算所得

Project Struct

├──entry/src/main/ets               // 代码区    
│  ├──common                        // 公共文件目录
│  │  └──constants                  
│  │     └──Constants.ets           // 常量
│  ├──entryability
│  │  └──EntryAbility.ts            // 应用的入口
│  ├──model                         
│  │  └──DataModel.ets              // 模拟数据
│  ├──pages
│  │  └──RankPage.ets               // 入口页面
│  ├──view                          // 自定义组件目录
│  │  ├──ListHeaderComponent.ets
│  │  ├──ListItemComponent.ets
│  │  └──TitleComponent.ets
│  └──viewmodel        
│     ├──RankData.ets               // 实体类
│     └──RankViewModel.ets          // 视图业务逻辑类
└──entry/src/main/resources        // 资源文件目录

Resource

资源文件(文字,尺寸,颜色,图片等资源)存储在 resources 目录下,通过 $r('app.string.login_text') 访问。

Tips

  • 代码中有错误,但是编辑器没提示,重新运行使用的是上一次 build 成功的代码重新打的包,需要手动去重新 Build → Rebuild Project
  • 嵌套对象更新内部数据需要特殊处理
  • 三方库:官方库中心开源汇总库

Summary

必要的点基本都照顾到了,布局方式,数据请求,状态处理,基本过一遍就能上手开发了,除了状态这块比较奇葩外,其他的也没啥大毛病。

下一篇可能会写写实战中暴露出来的小问题,也可能不会写了,毕竟写这玩意纯纯为爱发电,上架还得申请专利一堆乱七八糟的东西。

本文链接:https://note.lilonghe.net//post/harmonyos-kai-fa-chu-jie-chu.html

-- EOF --