近俩月有点颓废了,之前是每天下班后都学点啥,但现在已经不是一个人了嘛,回到家就一起瘫着看小说刷视频,也没学啥新东西,做了个记账的小程序用着还挺好,想着看能不能挪到 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
按照图上流程,将数据请求放在 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
必要的点基本都照顾到了,布局方式,数据请求,状态处理,基本过一遍就能上手开发了,除了状态这块比较奇葩外,其他的也没啥大毛病。
下一篇可能会写写实战中暴露出来的小问题,也可能不会写了,毕竟写这玩意纯纯为爱发电,上架还得申请专利一堆乱七八糟的东西。