快应用开发快速上手及简明教程

博主的开发及调试环境是 macOS 10.13.4 + Chrome/65.0.3325.181 + honorV9 EMUI8.0.0(Android8.0.0)

本文适合有一定前端开发经验的小伙伴(有一定经验看原文档太累赘了,而且环境配置部分原文写的太零碎了),最后总结了一些开发过程中遇到的坑。附文档链接:https://doc.quickapp.cn/

本文没有提到的部分和正常前端开发保持一致,也可能是我还没有遇到的坑。。。

注册账号

首先你需要一个手机厂商对应开发者账号和快应用账号

由于博主的手机是华为,就在华为官网注册一个个人开发者账号就好啦,这个部分就不具体展开了。相关地址快应用也给我提供了一份列表和指南。值得说明的是,这个账号是需要实名制的,有上传身份证照片和个人照片审核的,审核需要1-2个工作日(华为使用芝麻信用认证可以即即刻生效,不知道其他厂家什么情况)。

然后打开快应用官网 https://www.quickapp.cn/, 点击右上角的注册,注册一个快应用账号,这个部分很简单,也不展开了。
登陆以后我们可以看到导航栏上多出来一个开发者中心标签,点击进去,选择【厂商账号绑定】选项卡,选择你的手机品牌方标签进行绑定即可,目前小米、华为、金立、魅族、努比亚、OPPO 和 VIVO 都已经可以绑定了,而中兴、联想和一加还不能绑定。该绑定过程同样需要1-2个工作日审核。

安装相关软件和工具

开发工具

首先你需要安装 node v6.11.3 这是快应用官方推荐的版本
注意:不要使用 v8.0.* 这个版本内部 ZipStream 实现与 node-archive 包不兼容,会引起报错

如果你已经使用了 node 高版本,可以安装 nvm 管理 node 版本(如果你是第一次安装 node 可以直接安装 v6 版本,跳过该步骤)。
安装nvm, 注意不要使用 brew 安装,因为 curl 安装不需要手动配置 .bashrc :

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.30.2/install.sh | bash

然后安装对应版本node:

nvm install v6.11.3

检查当前使用 node 版本:

nvm current

此时应该已经是我们需要的版本了,如果不是,可以手动切换。查看已安装的 node 版本,和切换到已安装的版本:

nvm ls   # 查看已安装版本
nvm use v6.11.3     # 使用已安装版本

更多 nvm 用法直接输入:

nvm --help

到这里,我们继续快应用开发,全局安装脚手架:

npm install -g hap-toolkit

检测是否安装成功:

hap -V

调试工具

chrome 的 devTools 肯定是必不可少的。除此之外我们需要在手机安装一下两个应用:快应用调试器(左),快应用预览平台(右)

如果你不安装【快应用预览平台】,那么【快应用调试器】中的按钮都是不可点击的。而【快应用预览平台】里面其实啥也看不到,就是一个供快应用工作的壳。完整安装好以后【快应用调试器】如下图。

当然官方也给了一份 源码, 方便大家熟悉生命周期,样式,自定义组件,事件传递,组件使用。注意:下载后请记得操作:hap update --force,增加编译支持。

最后 adb 安装(Homebrew):

brew cask install android-platform-tools

检测是否安装成功:

adb devices

Demo

项目生成

我们利用脚手架新建一个项目, 并且进入该项目, init 过程中需要输入项目名称:
shell
hap init demo && cd demo

注意:之后的所有操作都在这个目录下面

这是 demo 目录结构

├── sign                      #rpk包签名模块
│   └── debug                 #调试环境
│       ├── certificate.pem   #证书文件
│       └── private.pem       #私钥文件
├── src
│   ├── Common                #公用的资源和组件文件
│   │   └── logo.png          #应用图标
│   ├── Demo                  #页面目录
│   |   └── index.ux          #页面文件,可自定义页面名称
│   ├── app.ux                #APP文件,可引入公共脚本,暴露公共数据和方法等
│   └── manifest.json         #项目配置文件,配置应用图标、页面路由等
└── package.json              #定义项目需要的各种模块及配置信息

需要注意的是,sign 用来存放签名模块,sign/debug 中有用于调试的证书的私钥,但debug签名由于是公开的,安全性无法保证。在 release 发布之前一定要添加 release 目录并且写入对应的证书和私钥:

openssl req -newkey rsa:2048 -nodes -keyout private.pem -x509 -days 3650 -out certificate.pem
mkdir sign/release && mv *.pem ./sign/release/

安装相关依赖:

npm install

如果上面的安装很慢,可以使用淘宝的资源:

 npm install --registry=https://registry.npm.taobao.org

脚手架已经提供很多运行方式:

npm run release     # 发布程序包,在 /dist/.signed.rpk,注意需要使用 release 签名模块
npm run build       # 生成 build 和 dist 两个目录。前者是临时产出,后者是最终产出
npm run watch       # 文件保存时自动编译和调试

调试方法

项目已和产生了 rpk 包,在做好之前的准备工作已后,运行:

npm run server        # 当然,你可以通过 --port XXXX 指定端口,默认12306

此时,会在控制台和 http://localhost:12306 得到一个二维码,利用【快应用调试器】中的扫码安装,即可在手机上看到效果了。
此时你退出预览界面,点击【快应用调试器】中的开始调试,会同步在 chrome Devtool 中打开调试窗口,原理同在 chrome://inspect 中使用的远程调试功能,如下图:

调试可以采用一下三种方式:

npm run build # 手动编译 + 手动刷新
npm run build && npm run notify # 手动编译 + 自动刷新
npm run watch # 自动编译 + 自动刷新

注意:使用远程调试请确保手机与PC在同一局域网

开发

  • IDE / Code Editor

    1. VS Code: 搜索 Hap Extension 安装插件即可
    2. webStorm: 可以通过 html 关联 ‘.ux’ 文件
    3. sublime: 选择 html 高亮即可
    4. Android Studio: 使用 Android Monitor 看 console
  • console

    为了正常使用 console.log 修改 src/manifest.json 中的 config 如下:

    {
     "config": {
      "logLevel": "debug"
    }
    }

    console 仅支持 info, log, warn, error, debug 方法。

  • LESS 支持

    1. 安装 less、less-loader
    2. 在 style 标签上添加 lang="less" 属性即可
  • Async Function 支持

    1. 安装 babel-runtime
    2. 将 babel 注入项目全局
      /* app.ux 文件(如果没有自己在 Common 里建一个)*/
      
      <script>
      const global = Object.getPrototypeOf(global) || global
      global.regeneratorRuntime = require ('babel-runtime/regenerator')
      
      // else code...
      script>

目录结构 与 manifest

目录结构

根目录下的 sign 文件上文已经提到过,其他文件目录不再赘述,因为前端项目大多如此,这里仅仅说 src 目录:

src
├── manifest.json          # 配置文件
├── app.ux                 # 入口文件
├── Page1                  # 页面1
│   ├── page1.ux
├── Page2                  # 页面2
│   ├── page2.ux
└── Common                 # 公共页面和资源
    ├── ComponentA.ux
    ├── ComponentB.ux
    └── xxx.png

manifest

manifest

属性 类型 默认值 必填 描述
package String - 应用包名,确认与原生应用的包名不一致,推荐采用com.company.module的格式,如:com.example.demo
name String - 应用名称,6个汉字以内,与应用商店保存的名称一致,用于在桌面图标、弹窗等处显示应用名称
icon String - 应用图标,提供192x192大小的即可
versionName String - 应用版本名称,如:”1.0”
versionCode Integer - 应用版本号,从1自增,推荐每次重新上传包时versionCode+1
minPlatformVersion Integer - 支持的最小平台版本号,兼容性检查,避免上线后在低版本平台运行并导致不兼容;如果不填按照内测版本处理
features Array - 接口列表,绝大部分接口都需要在这里声明,否则不能调用,详见每个接口的文档说明
config Object - 系统配置信息,详见下面说明
router Object - 路由信息,详见下面说明
display Object - UI显示相关配置,详见下面说明

config

用于定义系统配置和全局数据。

属性 类型 默认值 描述
logLevel String log 打印日志等级,分为off,error,warn,info,log,debug
designWidth Integer 750 页面设计基准宽度,根据实际设备宽度来缩放元素大小
data Object - 全局数据对象,属性名不能以$或_开头,在页面中可通过this进行访问;如果全局数据属性与页面的数据属性重名,则页面初始化时,全局数据会覆盖页面中对应的属性值

router

用于定义页面的组成和相关配置信息,如果页面没有配置路由信息,则在编译打包时跳过。

属性 类型 默认值 描述
entry String - 首页名称
pages Object - 页面配置列表,key值为页面名称(对应页面目录名,例如Hello对应’Hello’目录),value为页面详细配置page,详见下面说明

router.page

用于定义单个页面路由信息。

属性 类型 默认值 必填 描述
component String - 页面对应的组件名,与ux文件名保持一致,例如’hello’ 对应 ‘hello.ux’
path String /<页面名称> 页面路径,例如“/user”,不填则默认为/<页面名称>。path必须唯一,不能和其他page的path相同。下面page的path因为缺失,会被设置为“/Index”:"Index": {"component": "index"}
filter Object - 声明页面可以处理某种请求

router.page.filter

声明页面可以处理某种请求,页面可以从$page获取打开页面的参数。filter的结构如下:

"filter": {
  "": {
    "uri": ""
  }
}
属性 类型 默认值 必填 描述
action String - 请求的动作,目前仅支持view这一种
uri Pattern - 请求的数据的匹配规则。必须是正则表达式。如https?://.*可以匹配所有http和https类型的网址。

display

用于定义与UI显示相关的配置。

属性 类型 默认值 描述
backgroundColor String ffffff 窗口背景颜色
fullScreen Boolean false 是否是全屏模式,默认不会同时作用于titleBar,titleBar需要继续通过titleBar控制
titleBar Boolean true 是否显示titleBar
titleBarBackgroundColor String - 标题栏背景色
titleBarTextColor String - 标题栏文字颜色
titleBarText String - 标题栏文字(也可通过页面跳转传递参数(titleBarText)设置)
menu Boolean false 是否显示标题栏右上角菜单按钮
pages Object - 各个页面的显示样式,key为页面名(与路由中的页面名保持一致),value为窗口显示样式,页面样式覆盖default样式。

template 结构


<import name="hint" src="./hint-modal">import>  
<import src="./table">import>  
<template>
  <div class="container">
      <div class="mod-header">
          <text class="mod-title" style="color: red; margin: 10px;">{{title}}text>    
          <text class="mod-detail" onclick="showDetail">?text>    
      div>
      <div class="mod-content">
          
          <block for="totalData">   
              
              <div onclick="onTabClick($idx)" class="item {{tabIndex === $idx && 'active'}}"> 
                  <text class="{{tabIndex === $idx && 'text-active'}}">{{($item || {}).name}}text>
                  <text class="{{tabIndex === $idx && 'text-active'}}">{{($item || {}).value}}text>  
              div>
          block>
      div>
      <image class="mod-like" if="{{isLike}}" /> 
      <image class="mod-dislike" else />
      <table data={{dataList}}>table>  
      <hint show="{{isHintShown}}">
          This is children of hint templete.
      hint>   
      
  div>
template>

<style lang="less" src="./lessFile.less">style>   
<style lang="less">
  /* 引入外部 CSS/LESS */
  @import '../Common/global.less';

  .container{
    /* 定义样式,less 支持 */
  }
style>

<script>
  import fetch from "@system.fetch"    // 引入系统 js
  import conf from './globalConf';     // 引入外部 js
  export default {
    props: ['title', 'dataList'],  // 传入属性:必须字母开头,全小写、数字和 `-` ,不能保留字和函数,不能以符号开头
    public: {
      // 定义变量,会被 props 和内部请求覆盖
    },
    private: {
      // 定义变量,不会被 props 覆盖
    },
    protected: {
      // 定义变量,不会被 props 覆盖, 但会被内部请求覆盖(获得通过 a 标签和 router 传递的参数)
    }
    data :{   // data 不能和 public、private、protected 一起使用,data 也可以是 function(返回 data 对象,onInit之前执行)
      // 定义变量:不能保留字和函数,不能以符号开头
      totalData: [{name: 'a',value: 97},{name: 'b',value: 98}];
        // 定义变量,会被 props 覆盖
    },
    onTabClick(index){    // 内部事件定义
      console.log(index);
    },
    events: {
       onIDChange(){
          // 外部事件定义
       }
    }
  }
script>


<template>
  <text><slot>slot>text>          
template>

开发基础

保留字

除了传统保留字,添加了 show tid 等;

生命周期

页面生命周期

属性 类型 参数 返回值 描述 触发时机
onInit Function 监听页面初始化 当页面完成初始化时调用,只触发一次
onReady Function 监听页面创建完成 当页面完成创建可以显示时触发,只触发一次
onShow Function 监听页面显示 当进入页面时触发
onHide Function 监听页面隐藏 当页面跳转离开时触发
onDestroy Function 监听页面退出 当页面跳转离开(不进入导航栈)时触发
onBackPress Function Boolean 监听返回按钮动作 当用户点击返回按钮时触发。返回true表示页面自己处理返回逻辑,返回false表示使用默认的返回逻辑,不返回值会作为false处理
onMenuPress Function 监听菜单按钮动作 当用户点击菜单按钮时触发

A页面的生命周期接口的调用顺序:

  1. 打开页面A:onInit() -> onReady() -> onShow()
  2. 在页面A打开页面B:onHide()
  3. 从页面B返回页面A:onShow()
  4. A页面返回:onBackPress() -> onHide() -> onDestroy()

应用生命周期

属性 类型 参数 返回值 描述 触发时机
onCreate Function 监听应用创建 当应用创建时调用
onDestroy Function 监听应用销毁 当应用销毁时触发

预置对象

全局对象 (通过 this 访问)

的属性 类型 参数 描述
$app Object - 应用对象
app. a p p . def Object - 获取在app.ux中暴露的对象
app. a p p . data Object - 获取在manifest.json的config.data中声明的全局数据
$page Object - 页面对象
$page.action String - 获取打开当前页面的action。仅在当前页面是通过filter匹配的方式打开时有效,否则为undefined。参见manifest
$page.uri String - 获取打开当前页面的uri。仅在当前页面是通过filter匹配的方式打开时有效,否则为undefined。参见manifest
$page.setTitleBar Function Object* -
$valid Boolean - 页面对象是否有效
$visible Boolean - 页面是否处于用户可见状态

* this.$page.setTitleBar 参数属性包括:

{
  text: 'Hello QuickApp',        //标题栏文字
  textColor: '#ffff',            //文字颜色
  backgroundColor: '#434343',    //背景颜色
  backgroundOpacity: '0.8',      //背景透明度
  menu: false,      //是否在标题栏右上角显示菜单按钮 | 设置当前
}
属性 类型 参数 描述
$element Function id: String 获取指定id的组件dom对象,如果没有指定id,则返回根组件dom对象用法:this.$element('xxx')获取id为xxx的组件实例对象 this.$element() 获取根组件实例对象
$root Function 获取顶层ViewModel
$parent Function 获取父亲ViewModel

child|Function|id:String|idViewModelthis. c h i l d | F u n c t i o n | i d : S t r i n g | 获 取 指 定 i d 的 自 定 义 组 件 的 V i e w M o d e l 用 法 : t h i s . child(‘xxx’) 获取id为xxx的div组件ViewModel
vmdeprecated|Function|id:String|使this. v m ‘ d e p r e c a t e d ‘ | F u n c t i o n | i d : S t r i n g | 请 使 用 上 面 t h i s . child(‘xxx’)替代
rootElementdeprecated|Function||使this. r o o t E l e m e n t ‘ d e p r e c a t e d ‘ | F u n c t i o n | 无 | 请 使 用 上 面 t h i s . element()替代
$forceUpdate | Function | 无 | 强制页面刷新

公共属性 类型 参数 描述
$set Function key: String
value: Any
添加数据属性,必须在onInit函数中使用,用法:this.$set('key',value)
$delete Function key: String 删除数据属性,如果在onInit函数中使用,用法:this.$delete('key')
元素属性/方法 类型 参数 描述
$set Function key: String
value: Any
添加数据属性,用法:this.$vm('id').$set('key',value)
$delete Function key: String 删除数据属性,用法:this.$vm('id').$delete('key')
$on Function eventName: String
handler: Function
在当前页面注册监听事件, 可监听$emit()$dispatch()$broadcast()等触发的自定义事件,不能用于注册组件节点的事件响应
$off Function eventName: String
handler: Function
移除事件监听,参数 fnHandler 为可选,传递仅移除指定的响应函数,不传递则移除此事件的所有监听

emit|Function|eventName:String<br/>data:Object| e m i t | F u n c t i o n | e v e n t N a m e : S t r i n g < b r / > d a t a : O b j e c t | 触 发 当 前 实 例 监 听 事 件 函 数 , 与 on() 配合使用

* 注意,获取元素应该在页面已渲染后,如 onReady 事件中或 onReady 事件执行完以后。

页面设计

  • 布局和尺寸

    1. 采用 border-box 模型且不支持 box-sizing 属性
    2. 设计稿1px / 设计稿基准宽度 = 框架样式1px / 项目配置基准宽度(项目配置基准宽度:/src/manifest.jsonconfig.designWidth 的值,默认750)
  • CSS

    1. 可以使用内联样式、tag选择器、class选择器、id选择器来为组件设置样式
    2. 仅可以使用并列选择、后代选择器、子代选择器
    3. 支持@import引入外部样式、内联样式、行内样式
    4. 颜色值不支持缩写,伪类支持不完全(支持:disabled,:checked,:focus等)

通用

  1. 通用事件:click, longpress, focus, blur, appear(组件出现),disappear(组件消失),swipe(快速滑动,参数direction:[left|right|up|down])
  2. 通用属性: id, class, style, if, elif, else, for, show, disabled 等;
  3. 通用样式:width, height, padding, padding-, margin, margin-, border, border-style, border-width, border-color, border--color, border--width, border-radius, border---radius, background-color, background-size, background-image(仅本地图片), background-repeat, opacity, display(flex|none), flex, flex-grow, flex-shrick, flex-basis, position(none|fix), linear-gradient, repeating-inear-gradient, transform-origin, animation, animation-name, animation-delay, animation-duration, animation-iteration-count, animation-timing-function, animation-fill-mode, @key-frames(background-color|opacity|width|height|transform), transform(translate|translateX|translateY|rotate|rotateX|rotateY|scale|scaleX|scaleY)(以上*代表枚举[left|right|top|bottom], 具体和 css 一致。注:缩写形式和展开形式不要同时使用)

组件

默认支持通用事件、属性和样式
组件为文本容器组件,其它组件不能直接放置文本内容