鸿蒙开发记录
鸿蒙开发记录
本次属于入门学习,此处只记录少部分开发中遇到的 bug。
HML 的问题
HML
文件和 HTML
文件比较类似,但是鸿蒙中 div
等标签默认都是 flex
布局,可以不用像 Web 开发中那样显式地指定 display
:
<!-- display 属性的默认值为 flex -->
<div style="display : flex;">
<!-- ... -->
</div>
且其可选值仅有 3 个:
flex
:弹性布局grid
:网格布局none
:不渲染此元素
下面是开发过程中遇到的问题:
block 组件
block
对 show
属性的支持不友好:
类似地,使用 else
属性时:
条件渲染
自定义组件的 show
属性存在问题,例如:
<page1 show="{{ pageIdx == 0 }}"></page1>
<page2 show="{{ pageIdx == 1 }}"></page2>
<page3 show="{{ pageIdx == 2 }}"></page3>
<page4 show="{{ pageIdx == 3 }}"></page4>
显然以上 4 个条件不可能同时满足,但是这 4 个页面,居然是同时显示的(真机和预览器效果一致):
CSS 的『问题』
css 单位
鸿蒙中 css
不支持 em
、rem
、vh
、vw
等单位。
另外发现,font-size
属性不能使用百分比作为单位。
background 属性
在 Web 开发中,css
中的 background
是复合属性,我们可能会常常使用类似:
.class1 {
background: red;
}
的方式来设置背景颜色,但是这在鸿蒙中是不允许的,正确的方式如下:
/* 设置单一背景颜色用 background-color 属性 */
.class1 {
background-color: red;
}
/* 设置渐变背景颜色用 background 属性 */
.class2 {
background: linear-gradient(
90deg,
rgba(122, 202, 255, 1) 0%,
rgba(122, 202, 255, 1) 0%,
rgba(54, 163, 246, 1) 100%,
rgba(54, 163, 246, 1) 100%
);
}
选择器
鸿蒙基于 js
扩展的开发中,css
只支持以下几种 选择器:
值得注意的是,在 Web 开发种常用的组合选择器 .class1.class2
在这里是不支持的。
样式继承
文档显示字体等样式能够继承自父节点,但实际发现 text
组件的字体并不能够继承:
测试的代码可以是:
<div
style="flex-direction : column; justify-content : center;
align-items : center;"
>
<div style="font-size : 20px; justify-content : center;">
<text> Hello {{ title }} </text>
</div>
<text style="font-size : 20px;"> Hello {{ title }} </text>
</div>
渲染结果:
可以发现,第一个 text
组件并没有继承它的父节点 div
的 font-size
,而是使用了默认值 30px
。
样式预编译
css
中重复写选择器是非常恼人的,因此我想使用 scss
来做样式预处理。
在文档中可以看到,只需要将 .css
后缀修改成 .scss
即可使用:
看上去很美好,然而在修改后缀名之后,事实是:
- IDE 未能识别
.scss
类型的文件;
- IDE 没有代码提醒,和普通文本编辑无差异;
- Previewer 无法正常使用(编译失败);
预览器的日志如下:
Module not found: Error: Can't resolve './login.css' in '\entry\src\main\js\default\pages\login_main'
结论:DevEco 并不像文档中说的支持样式预处理。
当然,办法总比困难多,我个人使用的「解决方案」是通过另一款编辑器 VS Code,结合 Easy Sass 插件将 scss
编译成 css
:
这种方案的优点是,css
文件实时编译,DevEco 中也能够实时同步,Previewer 也能做到实时预览,开发体验 +99!
缺点则是部分 css
属性与鸿蒙中可使用的属性不完全兼容,或者鸿蒙中自带组件支持的 css
属性没有提示(如 swiper
组件的 indicator-color
属性)。
JS 的『问题』
console
在 js 中,我们可以使用逗号将我们要打印的参数隔开:
console.log(1.5, "example", 2);
浏览器中的打印结果:
而在鸿蒙中,以上代码的打印得到的是:
1.5
是的,从第二个往后的参数都被忽略了!
因此在鸿蒙中我们需要使用字符串拼接的方式来打印:
console.log(1.5 + " " + "example" + " " + 2);
console.log(`${1.5} ${"example"} ${2}`);
这可能是因为鸿蒙选择了用于物联网的超轻量引擎 JerryScript
,不同于 Chrome
的 v8
引擎。
计算属性
计算属性不能用箭头函数,这里并不是鸿蒙的问题,而是习惯使用箭头函数的人(比如我)容易踩到的坑:
export default {
data: {
title: "World",
},
computed: {
// 这种方式会导致 hml 渲染异常
test: () => {
return this.title + "!!!";
},
},
};
test
改成如下即可:
// 第1种
test() {
return this.title + "!!!";
}
// 第2种
test: function() {
return this.title + "!!!";
}
// 第3种
test: {
// get 方法也不能用箭头函数
get() {
return this.title + "!!!";
},
}
这属于 js
中闭包的问题,箭头函数中 this
和普通函数中 this
的指向是不同的。
实际上除了计算属性,
data
和普通方法使用箭头函数也会出问题,建议只在方法内部使用箭头函数,例如网络请求的回调函数等等。
网络请求
这里其实网络请求本身并没有问题,但本人在实际使用时发现需要注意两个问题。
顺序问题
按文档中的做法:
import http from "@ohos.net.http";
// 每一个httpRequest对应一个http请求任务,不可复用
let httpRequest = http.createHttp();
这个注释中的「不可复用」十分重要,起初我的使用方法是:
import http from "@ohos.net.http";
const httpRequest = http.createHttp();
export default {
data: {
result: "",
},
onInit() {
httpRequest
.request("https://example.com/api/", {
method: "GET",
})
.then(({ result }) => {
this.result = result;
});
},
};
发现该请求只会在首次进入该页面时触发,之后重新进入该页面 result 始终为空(页面不发生变化)。
此处的实际场景是通过导航栏切换页面。
随后我修改代码如下:
import http from "@ohos.net.http";
import prompt from "@system.prompt";
export default {
data: {
httpRequest: null,
},
onInit() {
this.httpRequest = http.createHttp();
},
// 通过点击某个按钮测试
exampleMethod() {
this.httpRequest
.request("https://example.com/api/", {
method: "GET",
})
.then(({ result }) => {
prompt.showToast({
message: result,
});
});
},
};
发现与先前代码出现的问题类似:只有首次点击会生效。
以上两种版本的代码有一个共同的问题,那就是 http.createHttp()
使用的都是同一个值!因此出现只有首次生效的现象就是因为那句「不可复用」!
找到原因后解决起来就简单了,以第 1 个版本的代码为例:
import http from "@ohos.net.http";
- const httpRequest = http.createHttp();
export default {
data: {
result: "",
},
onInit() {
+ const httpRequest = http.createHttp();
httpRequest.request("https://example.com/api/", {
method: "GET",
}).then(({ result }) => {
this.result = result;
});
},
};
是的,只需要移动那一行代码的位置!
返回的结果
我所请求的接口的结构大致如下:
{
"code": 200,
"msg": "成功",
"data": {
"id": 1,
"username": "trezedo",
"phone": "13800000000"
}
}
在鸿蒙的 http
请求的回调函数中,data.result
的类型是 string
,我们可以这样检测:
httpRequest
.request("https://example.com/api/", {
method: "GET",
})
.then(({ result }) => {
prompt.showToast({
message: typeof result,
});
});
因此如果我们要访问上面 json
结构中的属性,必须先将 result
解析成对象:
result = JSON.parse(result);
console.log(`msg: ${result.msg}\n data: ${result.data}`);
访问公共代码
这里所遇到的应该算 DevEco IDE 的代码提示问题。
第一种,自行插入路径。
比如我们在 common/js/utils.js
中定义了函数:
export const isPhoneValid = (phone) =>
/^1(3\d|5[0-3,5-9]|7[1-3,5-8]|8\d)\d{8}$/.test(phone);
然后我们想在 JS Page 中导入 isPhoneValid
这个函数(相对路径为 "../../common/js/utils.js"
),
在我们输入 "../"
后,会有以上提示,但是再按下一个点 .
,它会自行插入上面蓝色部分的 index
:
第二种则是 DevEco 不能正确提示:
在 js
文件路径正确的情况下,在其他文件导入时不能正确提示公共文件中导出的方法。
希望以上问题在今后能够修复吧!
常用配置
去掉应用顶部标题
在 config.json
中的 module
或 module.abilities
中添加:
"metaData": {
"customizeData": [
{
"name": "hwc-theme",
"value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"
}
]
}
例如:
{
// ...
"module": {
"abilities": [
{
"name": "com.example.helloworld.MainAbility",
// ...
"metaData": {
"customizeData": [
{
"name": "hwc-theme",
"value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"
}
]
}
}
]
}
}
在开发者文档中搜索了很多,实际测试发现如果一个项目中有多个 module ,那么在 自定义标题栏实战 中介绍的方式(在 module
下添加)无效果。
网络权限
在 module
中添加 reqPermissions
,其值是一个数组:
{
// ...
"module": {
"reqPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
如果要支持 http
协议的网络接口,则还需要设置:
{
"deviceConfig": {
"default": {
"network": {
"cleartextTraffic": true
}
}
}
}
总结
鸿蒙基于 js
扩展的类 Web 开发方式在很大程度上与前端工程化开发方式类似,其语法和 vue2
或 小程序 应用开发基本一样,支持 data
、props
、computed
、watch
和 methods
,,也能创建自定义的组件(JS Component)。这里我认为鸿蒙 watch
的语法更像 vue3
的组合 API。
但目前可能 IDE 等尚未成熟,使用模拟器、远程真机调试等操作不是很方便。若只是做界面,使用 Previewer 预览的开发体验能提升很多;但若要做交互就相对麻烦许多,例如不能通过电脑键盘输入等,这也是我更愿意使用本地真机进行调试的原因。希望随着鸿蒙的迭代升级,开发者能拥有更友好的开发体验。