跳至主要內容

鸿蒙开发记录

zedo2022年7月10日HarmonyOS鸿蒙ArkUI大约 8 分钟约 2313 字

鸿蒙开发记录

本次属于入门学习,此处只记录少部分开发中遇到的 bug。

HML 的问题

HML 文件和 HTML 文件比较类似,但是鸿蒙中 div 等标签默认都是 flex 布局,可以不用像 Web 开发中那样显式地指定 display

<!-- display 属性的默认值为 flex -->
<div style="display : flex;">
    <!-- ... -->
</div>

且其可选值仅有 3 个:

下面是开发过程中遇到的问题:

block 组件

blockshow 属性的支持不友好:

image-20220630100120

类似地,使用 else 属性时:

image-20220630100259

条件渲染

自定义组件的 show 属性存在问题,例如:

<page1 show="{{ pageIdx == 0 }}"></page1>
<page2 show="{{ pageIdx == 1 }}"></page2>
<page3 show="{{ pageIdx == 2 }}"></page3>
<page4 show="{{ pageIdx == 3 }}"></page4>

显然以上 4 个条件不可能同时满足,但是这 4 个页面,居然是同时显示的(真机和预览器效果一致):

image-20220630100556

CSS 的『问题』

css 单位

鸿蒙中 css 不支持 emremvhvw 等单位。

另外发现,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 只支持以下几种 选择器open in new window

image-20220709212122

值得注意的是,在 Web 开发种常用的组合选择器 .class1.class2 在这里是不支持的。

样式继承

文档显示字体等样式能够继承自父节点,但实际发现 text 组件的字体并不能够继承:

image-20220709212528

测试的代码可以是:

<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>

渲染结果:

image-20220709213004

可以发现,第一个 text 组件并没有继承它的父节点 divfont-size,而是使用了默认值 30px

样式预编译

css 中重复写选择器是非常恼人的,因此我想使用 scss 来做样式预处理。

在文档中可以看到,只需要将 .css 后缀修改成 .scss 即可使用:

image-20220709213744

看上去很美好,然而在修改后缀名之后,事实是:

预览器的日志如下:

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

image-20220709215143

这种方案的优点是,css 文件实时编译,DevEco 中也能够实时同步,Previewer 也能做到实时预览,开发体验 +99!

缺点则是部分 css 属性与鸿蒙中可使用的属性不完全兼容,或者鸿蒙中自带组件支持的 css 属性没有提示(如 swiper 组件的 indicator-color 属性)。

JS 的『问题』

console

在 js 中,我们可以使用逗号将我们要打印的参数隔开:

console.log(1.5, "example", 2);

浏览器中的打印结果:

image-20220709223850

而在鸿蒙中,以上代码的打印得到的是:

1.5

是的,从第二个往后的参数都被忽略了!

因此在鸿蒙中我们需要使用字符串拼接的方式来打印:

console.log(1.5 + " " + "example" + " " + 2);
console.log(`${1.5} ${"example"} ${2}`);

这可能是因为鸿蒙选择了用于物联网的超轻量引擎 JerryScript,不同于 Chromev8 引擎。

计算属性

计算属性不能用箭头函数,这里并不是鸿蒙的问题,而是习惯使用箭头函数的人(比如我)容易踩到的坑:

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"),

image-20220709231931

在我们输入 "../" 后,会有以上提示,但是再按下一个点 . ,它会自行插入上面蓝色部分的 index

image-20220709232251

第二种则是 DevEco 不能正确提示:

js 文件路径正确的情况下,在其他文件导入时不能正确提示公共文件中导出的方法。

image-20220709233227

希望以上问题在今后能够修复吧!

常用配置

去掉应用顶部标题

config.json 中的 modulemodule.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 ,那么在 自定义标题栏实战open in new window 中介绍的方式(在 module 下添加)无效果。

网络权限

module 中添加 reqPermissions,其值是一个数组:

{
    // ...
    "module": {
        "reqPermissions": [
            {
                "name": "ohos.permission.INTERNET"
            }
        ]
    }
}

如果要支持 http 协议的网络接口,则还需要设置:

{
    "deviceConfig": {
        "default": {
            "network": {
                "cleartextTraffic": true
            }
        }
    }
}

总结

鸿蒙基于 js 扩展的类 Web 开发方式在很大程度上与前端工程化开发方式类似,其语法和 vue2 或 小程序 应用开发基本一样,支持 datapropscomputedwatchmethods,,也能创建自定义的组件(JS Component)。这里我认为鸿蒙 watch 的语法更像 vue3 的组合 API。

但目前可能 IDE 等尚未成熟,使用模拟器、远程真机调试等操作不是很方便。若只是做界面,使用 Previewer 预览的开发体验能提升很多;但若要做交互就相对麻烦许多,例如不能通过电脑键盘输入等,这也是我更愿意使用本地真机进行调试的原因。希望随着鸿蒙的迭代升级,开发者能拥有更友好的开发体验。

2023-9-14 更新
重启 search-pro,css 样式调整